參考原創地址:https://blog.csdn.net/neosmith/article/details/61202084java
說明:使用動態數據源的初衷,是能在應用層作到讀寫分離,即在程序代碼中控制不一樣的查詢方法去鏈接不一樣的庫mysql
1.第一步 關閉spring boot的單數據源:spring
/** * 經過配置多數據源,實現動態數據源切換 * 1.禁用掉spring boot自帶的數據源處理類DataSourceAutoConfiguration.class。 * 2.在配置文件application.yml中配置鏈接多個數據操做 */ @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) @MapperScan("com.example.server.dao") public class ServerApplication { public static void main(String[] args) { SpringApplication.run(ServerApplication.class, args); }
2,第二步 定義一個ContextHolder, 用於保存當前線程使用的數據源名sql
/** * 使用動態數據源的初衷,是能在應用層作到讀寫分離,即在程序代碼中控制不一樣的查詢方法去鏈接不一樣的庫。除了這種方法之外, * 數據庫中間件也是個不錯的選擇,它的優勢是數據庫集羣對應用來講只暴露爲單庫,不須要切換數據源的代碼邏輯。 * 咱們經過自定義註解 + AOP的方式實現數據源動態切換。 * 首先定義一個ContextHolder, 用於保存當前線程使用的數據源名 * @author liuqiuping * @date 2018-4-21 */ public class DataSourceContextHolder { public static final Logger log = LoggerFactory.getLogger(DataSourceContextHolder.class); /** * 默認數據源 */ public static final String DEFAULT_DS = "Master"; private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); // 設置數據源名 public static void setDB(String dbType) { log.debug("切換到{}數據源", dbType); contextHolder.set(dbType); } // 獲取數據源名 public static String getDB() { return (contextHolder.get()); } // 清除數據源名 public static void clearDB() { contextHolder.remove(); } }
3 第三步 自定義一個javax.sql.DataSource接口的實現,這裏只須要繼承Spring爲咱們預先實現好的父類AbstractRoutingDataSource數據庫
package com.example.server.config; import com.alibaba.druid.pool.DruidDataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * 動態數據源配置, *自定義一個javax.sql.DataSource接口的實現,這裏只須要繼承Spring爲咱們預先實現好的父類AbstractRoutingDataSource * @author liuqiuping * @date 2018-4-21 */ public class DynamicDataSource extends AbstractRoutingDataSource { private static final Logger log = LoggerFactory.getLogger(DynamicDataSource.class); @Override protected Object determineCurrentLookupKey() { log.debug("數據源爲{}", DataSourceContextHolder.getDB()); return DataSourceContextHolder.getDB(); } }
4 第四步 建立自定義sqlSessionFactoryapache
import com.alibaba.druid.pool.DruidDataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import javax.annotation.Resource; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * 建立自定義sqlSessionFactory,實現多個數據源動態注入sqlSessionFactory * @author liuqiuping * @date 2018-4-21 */ @Configuration @MapperScan(basePackages = {"com.example.server.dao"}, sqlSessionFactoryRef = "sqlSessionFactory") public class MybatisDbAConfig { private static final Logger logger = LoggerFactory.getLogger(DynamicDataSource.class); @Bean(name = "Master") @ConfigurationProperties(prefix = "spring.datasource.master") // application.properteis中對應屬性的前綴 public DataSource MasterDataSource() { logger.info("注入 druid MasterDataSource!!!"); // DruidDataSource datasource = new DruidDataSource(); //// datasource.setUrl(propertyResolver.getProperty("url")); //// datasource.setDriverClassName(propertyResolver.getProperty("driver-class-name")); //// datasource.setUsername(propertyResolver.getProperty("username")); //// datasource.setPassword(propertyResolver.getProperty("password")); //// datasource.setInitialSize(Integer.valueOf(propertyResolver.getProperty("initialSize"))); //// datasource.setMinIdle(Integer.valueOf(propertyResolver.getProperty("minIdle"))); //// datasource.setMaxWait(Long.valueOf(propertyResolver.getProperty("maxWait"))); //// datasource.setMaxActive(Integer.valueOf(propertyResolver.getProperty("maxActive"))); //// datasource.setMinEvictableIdleTimeMillis(Long.valueOf(propertyResolver.getProperty("minEvictableIdleTimeMillis"))); //// return datasource; return DataSourceBuilder.create().build(); } @Bean(name = "Cluster") @ConfigurationProperties(prefix = "spring.datasource.cluster") // application.properteis中對應屬性的前綴 public DataSource clusterDataSource() { logger.info("注入 druid MasterDataSource!!!"); // DruidDataSource datasource = new DruidDataSource(); // datasource.setUrl(propertyResolver.getProperty("url")); // datasource.setDriverClassName(propertyResolver.getProperty("driver-class-name")); // datasource.setUsername(propertyResolver.getProperty("username")); // datasource.setPassword(propertyResolver.getProperty("password")); // datasource.setInitialSize(Integer.valueOf(propertyResolver.getProperty("initialSize"))); // datasource.setMinIdle(Integer.valueOf(propertyResolver.getProperty("minIdle"))); // datasource.setMaxWait(Long.valueOf(propertyResolver.getProperty("maxWait"))); // datasource.setMaxActive(Integer.valueOf(propertyResolver.getProperty("maxActive"))); // datasource.setMinEvictableIdleTimeMillis(Long.valueOf(propertyResolver.getProperty("minEvictableIdleTimeMillis"))); // return datasource; return DataSourceBuilder.create().build(); } @Bean(name="dynamicDataSource") @Primary //優先使用,多數據源 public DataSource dataSource() { DynamicDataSource dynamicDataSource = new DynamicDataSource(); DataSource master = MasterDataSource(); DataSource slave = clusterDataSource(); //設置默認數據源 dynamicDataSource.setDefaultTargetDataSource(master); //配置多數據源 Map<Object,Object> map = new HashMap<>(); map.put("Master", master); //key須要跟ThreadLocal中的值對應 map.put("Cluster", slave); dynamicDataSource.setTargetDataSources(map); return dynamicDataSource; } }
5 第五步,編寫AOP切面,實現切換邏輯,根據具體方法調用不用的數據源session
package com.example.server.config; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import java.lang.reflect.Method; /** * 編寫AOP切面,實現切換邏輯: * @author liuqiuping * @date 2018-4-21 */ @Aspect @Component public class DynamicDataSourceAspect { @Before("@annotation(DS)") public void beforeSwitchDS(JoinPoint point){ //得到當前訪問的class Class<?> className = point.getTarget().getClass(); //得到訪問的方法名 String methodName = point.getSignature().getName(); //獲得方法的參數的類型 Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes(); String dataSource = DataSourceContextHolder.DEFAULT_DS; try { // 獲得訪問的方法對象 Method method = className.getMethod(methodName, argClass); // 判斷是否存在@DS註解 if (method.isAnnotationPresent(DS.class)) { DS annotation = method.getAnnotation(DS.class); // 取出註解中的數據源名 dataSource = annotation.value(); } } catch (Exception e) { e.printStackTrace(); } // 切換數據源 DataSourceContextHolder.setDB(dataSource); } @After("@annotation(DS)") public void afterSwitchDS(JoinPoint point){ DataSourceContextHolder.clearDB(); } }
6 第六步 自定義DS註解mybatis
/** * 自定義DS註解 * @author liuqiuping * @date 2018-4-21 * */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD }) public @interface DS { String value() default "master"; }
到此完成多數據源的配置app
接下來配置兩個不一樣數據數據源的配置鏈接ide
spring: application: name: sso-server ## 選擇 數據源 datasource: master: url: jdbc:mysql://127.0.0.1:3306/sso?&useUnicode=true&characterEncoding=utf8 username: root password: 123 driver-class-name: com.mysql.jdbc.Driver initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 cluster: url: jdbc:mysql://127.0.0.1:3306/ssoCluster?&useUnicode=true&characterEncoding=utf8 username: root password: 123 driver-class-name: com.mysql.jdbc.Driver initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000
動態數據源測試
@DS("Master") @Override public User findByLoginName(String name) { User user = userMapper.findByLoginName(name); if(null!=user){ user.setRoles(roleMapper.selectByUserId(user.getId())); } return user; } /** * 使用數據源2 * @param loginName * @return */ @DS("Cluster") @Override public User findByLoginNameTest(String loginName) { return userMapper.findByLoginNameTest(loginName); }
親測,調用不一樣的方法,執行不一樣的數據查詢成功。