簡要原理:html
1)DataSourceEnum列出全部的數據源的key---keyjava
2)DataSourceHolder是一個線程安全的DataSourceEnum容器,並提供了向其中設置和獲取DataSourceEnum的方法mysql
3)DynamicDataSource繼承AbstractRoutingDataSource並重寫其中的方法determineCurrentLookupKey(),在該方法中使用DataSourceHolder獲取當前線程的DataSourceEnumredis
4)MyBatisConfig中生成2個數據源DataSource的bean---valuespring
5)MyBatisConfig中將1)和4)組成的key-value對寫入到DynamicDataSource動態數據源的targetDataSources屬性(固然,同時也會設置2個數據源其中的一個爲DynamicDataSource的defaultTargetDataSource屬性中)sql
6)將DynamicDataSource做爲primary數據源注入到SqlSessionFactory的dataSource屬性中去,而且該dataSource做爲transactionManager的入參來構造DataSourceTransactionManager數據庫
7)使用spring aop根據不一樣的包設置不一樣的數據源(DataSourceExchange),先使用DataSourceHolder設置將要使用的數據源keyapache
注意:在mapper層進行操做的時候,會先調用determineCurrentLookupKey()方法獲取一個數據源(獲取數據源:先根據設置去targetDataSources中去找,若沒有,則選擇defaultTargetDataSource),以後在進行數據庫操做。tomcat
package com.theeternity.common.dataSource; /** * @program: boxApi * @description: 多數據源枚舉類 * @author: tonyzhang * @create: 2018-12-18 11:14 */ public enum DataSourceEnum { /** * @Description: DS1數據源1, DS2數據源2 * @Param: * @return: * @Author: tonyzhang * @Date: 2018-12-18 11:20 */ DS1("ds1"), DS2("ds2"); private String key; public String getKey() { return key; } public void setKey(String key) { this.key = key; } DataSourceEnum(String key) { this.key = key; } }
做用:構建一個DatabaseType容器,並提供了向其中設置和獲取DataSourceEnmu的方法安全
package com.theeternity.common.dataSource; /** * @program: boxApi * @description: DynamicDataSourceHolder用於持有當前線程中使用的數據源標識 * @author: tonyzhang * @create: 2018-12-18 11:16 */ public class DataSourceHolder { private static final ThreadLocal<String> dataSources = new ThreadLocal<String>(); public static void setDataSources(String dataSource) { dataSources.set(dataSource); } public static String getDataSources() { return dataSources.get(); } }
做用:使用DatabaseContextHolder獲取當前線程的DataSourceEnmu
package com.theeternity.common.dataSource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * @program: boxApi * @description: DynamicDataSource的類,繼承AbstractRoutingDataSource並重寫determineCurrentLookupKey方法 * @author: tonyzhang * @create: 2018-12-18 11:17 */ public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceHolder.getDataSources(); } }
做用:
經過讀取application-test.yml文件生成兩個數據源(writeDS、readDS)
使用以上生成的兩個數據源構造動態數據源dataSource
@Primary:指定在同一個接口有多個實現類能夠注入的時候,默認選擇哪個,而不是讓@Autowire註解報錯(通常用於多數據源的狀況下)
@Qualifier:指定名稱的注入,當一個接口有多個實現類的時候使用(在本例中,有兩個DataSource類型的實例,須要指定名稱注入)
@Bean:生成的bean實例的名稱是方法名(例如上邊的@Qualifier註解中使用的名稱是前邊兩個數據源的方法名,而這兩個數據源也是使用@Bean註解進行注入的)
經過動態數據源構造SqlSessionFactory和事務管理器(若是不須要事務,後者能夠去掉)
package com.theeternity.beans.mybatisConfig; import com.theeternity.common.dataSource.DataSourceEnum; import com.theeternity.common.dataSource.DynamicDataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * @program: ApiBoot * @description: 動態數據源配置 * @author: TheEternity Zhang * @create: 2019-02-18 11:04 */ @Configuration public class MyBatisConfig { @Value("${spring.datasource.type}") private Class<? extends DataSource> dataSourceType; @Value("${mybatis.type-aliases-package}") private String basicPackage; @Value("${mybatis-plus.mapper-locations}") private String mapperLocation; @Bean(name="writeDS") @ConfigurationProperties(prefix = "primary.datasource.druid") public DataSource writeDataSource() { return DataSourceBuilder.create().type(dataSourceType).build(); } /** * 有多少個從庫就要配置多少個 * @return */ @Bean(name = "readDS") @ConfigurationProperties(prefix = "back.datasource.druid") public DataSource readDataSourceOne(){ return DataSourceBuilder.create().type(dataSourceType).build(); } /** * @Primary 該註解表示在同一個接口有多個實現類能夠注入的時候,默認選擇哪個,而不是讓@autowire註解報錯 * @Qualifier 根據名稱進行注入,一般是在具備相同的多個類型的實例的一個注入(例若有多個DataSource類型的實例) */ @Bean @Primary public DynamicDataSource dataSource(@Qualifier("writeDS") DataSource writeDS, @Qualifier("readDS") DataSource readDS) { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DataSourceEnum.DS1.getKey(), writeDS); targetDataSources.put(DataSourceEnum.DS2.getKey(), readDS); DynamicDataSource dataSource =new DynamicDataSource(); // 該方法是AbstractRoutingDataSource的方法 dataSource.setTargetDataSources(targetDataSources); // 默認的datasource設置爲writeDS dataSource.setDefaultTargetDataSource(writeDS); return dataSource; } /** * 根據數據源建立SqlSessionFactory */ @Bean public SqlSessionFactory sqlSessionFactory(DynamicDataSource dataSource) throws Exception { SqlSessionFactoryBean fb = new SqlSessionFactoryBean(); // 指定數據源(這個必須有,不然報錯) fb.setDataSource(dataSource); // 下邊兩句僅僅用於*.xml文件,若是整個持久層操做不須要使用到xml文件的話(只用註解就能夠搞定),則不加 // 指定基包 fb.setTypeAliasesPackage(basicPackage); fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocation)); return fb.getObject(); } /** * 配置事務管理器 */ @Bean public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception { return new DataSourceTransactionManager(dataSource); } }
package com.theeternity.common.aop; import com.theeternity.common.dataSource.DataSourceEnum; import com.theeternity.common.dataSource.DataSourceHolder; 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.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; /** * @program: boxApi * @description: 數據源自動切換AOP * @author: tonyzhang * @create: 2018-12-18 11:20 */ @Aspect @Component public class DataSourceExchange { private Logger logger= LoggerFactory.getLogger(DataSourceExchange.class); @Pointcut("execution(* com.theeternity.core.*.service..*(..))") public void pointcut(){} /** * @Description: 在service方法開始以前切換數據源 * @Param: [joinPoint] * @return: void * @Author: tonyzhang * @Date: 2018-12-18 11:28 */ @Before(value="pointcut()") public void before(JoinPoint joinPoint){ //獲取目標對象的類類型 Class<?> aClass = joinPoint.getTarget().getClass(); String c = aClass.getName(); System.out.println("做用包名:"+c); String[] ss = c.split("\\."); //獲取包名用於區分不一樣數據源 String packageName = ss[3]; System.out.println("包名:"+packageName); if ("AutoGenerator".equals(packageName)) { DataSourceHolder.setDataSources(DataSourceEnum.DS1.getKey()); logger.info("數據源:"+DataSourceEnum.DS1.getKey()); } else { DataSourceHolder.setDataSources(DataSourceEnum.DS2.getKey()); logger.info("數據源:"+DataSourceEnum.DS2.getKey()); } } /** * @Description: 執行完畢以後將數據源清空 * @Param: [joinPoint] * @return: void * @Author: tonyzhang * @Date: 2018-12-18 11:27 */ @After(value="pointcut()") public void after(JoinPoint joinPoint){ DataSourceHolder.setDataSources(null); } }
首先要將spring boot自帶的DataSourceAutoConfiguration禁掉,由於它會讀取application.properties文件的spring.datasource.*屬性並自動配置單數據源。在@SpringBootApplication註解中添加exclude屬性便可:
package com.theeternity.core; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; @SpringBootApplication(scanBasePackages = "com.theeternity",exclude = {DataSourceAutoConfiguration.class}) /** * 全局配置,掃描指定包下的dao接口,不用每一個dao接口上都寫@Mapper註解了 */ @MapperScan("com.theeternity.core.*.dao") public class CoreApplication { public static void main(String[] args) { SpringApplication.run(CoreApplication.class, args); } }
將MyBatisConfig中SqlSessionFactory的構建方法改成下面的
@Bean 2 public SqlSessionFactory sqlSessionFactory(@Qualifier("writeDS") DataSource writeDS, 3 @Qualifier("readDS") DataSource readDS) throws Exception{ 4 SqlSessionFactoryBean fb = new SqlSessionFactoryBean(); 5 fb.setDataSource(this.dataSource(writeDS, readDS)); 6 fb.setTypeAliasesPackage(basicPackage); fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocation)); 8 return fb.getObject(); 9 }
#配置使用的文件 spring: profiles: active: test,redis devtools: restart: enabled: true additional-paths: src/main/java #配置tomcat端口及路徑 server: port: 8088 servlet: context-path: /core #mybatis配置 mybatis: mapper-locations: classpath*:/mybatis-mapper/*.xml type-aliases-package: com.theeternity.core.*.entity configuration: map-underscore-to-camel-case: true #駝峯命名 #mybatis plus配置 mybatis-plus: mapper-locations: classpath*:/mybatis-plus-mapper/*.xml # MyBaits 別名包掃描路徑,經過該屬性能夠給包中的類註冊別名,註冊後在 Mapper 對應的 XML 文件中能夠直接使用類名,而不用使用全限定的類名 type-aliases-package: com.theeternity.core.*.entity # 數據庫表與實體類的駝峯命名自動轉換 configuration: map-underscore-to-camel-case: true
spring: datasource: #使用druid鏈接池 type: com.alibaba.druid.pool.DruidDataSource # 自定義的主數據源配置信息 primary: datasource: #druid相關配置 druid: #監控統計攔截的filters filters: stat driverClassName: com.mysql.cj.jdbc.Driver #配置基本屬性 url: jdbc:mysql://localhost:3306/wechatMVC?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false username: *** password: *** #配置初始化大小/最小/最大 initialSize: 1 minIdle: 1 maxActive: 20 #獲取鏈接等待超時時間 maxWait: 60000 #間隔多久進行一次檢測,檢測須要關閉的空閒鏈接 timeBetweenEvictionRunsMillis: 60000 #一個鏈接在池中最小生存的時間 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 'x' testWhileIdle: true testOnBorrow: false testOnReturn: false #打開PSCache,並指定每一個鏈接上PSCache的大小。oracle設爲true,mysql設爲false。分庫分表較多推薦設置爲false poolPreparedStatements: false maxPoolPreparedStatementPerConnectionSize: 20 # 自定義的從數據源配置信息 back: datasource: #druid相關配置 druid: #監控統計攔截的filters filters: stat driverClassName: com.mysql.cj.jdbc.Driver #配置基本屬性 url: jdbc:mysql://localhost:3306/mycrm?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false username: *** password: *** #配置初始化大小/最小/最大 initialSize: 1 minIdle: 1 maxActive: 20 #獲取鏈接等待超時時間 maxWait: 60000 #間隔多久進行一次檢測,檢測須要關閉的空閒鏈接 timeBetweenEvictionRunsMillis: 60000 #一個鏈接在池中最小生存的時間 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 'x' testWhileIdle: true testOnBorrow: false testOnReturn: false #打開PSCache,並指定每一個鏈接上PSCache的大小。oracle設爲true,mysql設爲false。分庫分表較多推薦設置爲false poolPreparedStatements: false maxPoolPreparedStatementPerConnectionSize: 20
https://www.cnblogs.com/java-zhao/p/5413845.html (主參考流程)
http://www.cnblogs.com/java-zhao/p/5415896.html (轉aop更改數據源)
https://blog.csdn.net/maoyeqiu/article/details/74011626 (將datasource注入bean簡單方法)
https://blog.csdn.net/neosmith/article/details/61202084(將spring boot自帶的DataSourceAutoConfiguration禁掉)