1、創建一個springboot項目,導入依賴(3.3.0)
<dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.6.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><!--Druid連接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.3</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>
2、實現ThreadLocal
創建一個類用于實現ThreadLocal,主要是通過get,set,remove方法來獲取、設置、刪除當前線程對應的數據源。
public class DataSourceContextHolder {//此類提供線程局部變量。這些變量不同于它們的正常對應關系是每個線程訪問一個線程(通過get、set方法),有自己的獨立初始化變量的副本。private static final ThreadLocal<String> DATASOURCE_HOLDER = new ThreadLocal<>();/*** 設置數據源* @param dataSourceName 數據源名稱*/public static void setDataSource(String dataSourceName){DATASOURCE_HOLDER.set(dataSourceName);}/*** 獲取當前線程的數據源* @return 數據源名稱*/public static String getDataSource(){return DATASOURCE_HOLDER.get();}/*** 刪除當前數據源*/public static void removeDataSource(){DATASOURCE_HOLDER.remove();}
}
3、實現AbstractRoutingDataSource
定義一個動態數據源類實現AbstractRoutingDataSource,通過determineCurrentLookupKey方法與上述實現的ThreadLocal類中的get方法進行關聯,實現動態切換數據源。
/*** @description: 實現動態數據源,根據AbstractRoutingDataSource路由到不同數據源中* @date: 2023/7/27 11:18**/
public class DynamicDataSource extends AbstractRoutingDataSource {public DynamicDataSource(DataSource defaultDataSource,Map<Object, Object> targetDataSources){super.setDefaultTargetDataSource(defaultDataSource);super.setTargetDataSources(targetDataSources);}@Overrideprotected Object determineCurrentLookupKey() {return DataSourceContextHolder.getDataSource();}
}
上述代碼中,還實現了一個動態數據源類的構造方法,主要是為了設置默認數據源,以及以Map保存的各種目標數據源。其中Map的key是設置的數據源名稱,value則是對應的數據源(DataSource)。
4、配置數據庫
application.yml中配置數據庫信息:
#設置數據源
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedruid:master:url: jdbc:mysql://xxxxxx:3306/test1?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=falseusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driverslave:url: jdbc:mysql://xxxxx:3306/test2?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=falseusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driverinitial-size: 15min-idle: 15max-active: 200max-wait: 60000time-between-eviction-runs-millis: 60000min-evictable-idle-time-millis: 300000validation-query: ""test-while-idle: truetest-on-borrow: falsetest-on-return: falsepool-prepared-statements: falseconnection-properties: false
mybatis:mapper-locations: mappers/**/*.xmlconfiguration:map-underscore-to-camel-case: truelog-impl: org.apache.ibatis.logging.stdout.StdOutImpl
配置類
/*** @description: 設置數據源* @date: 2023/7/27 11:34**/
@Configuration
public class DateSourceConfig {@Bean@ConfigurationProperties("spring.datasource.druid.master")public DataSource masterDataSource(){return DruidDataSourceBuilder.create().build();}@Bean@ConfigurationProperties("spring.datasource.druid.slave")public DataSource slaveDataSource(){return DruidDataSourceBuilder.create().build();}@Bean(name = "dynamicDataSource")@Primarypublic DynamicDataSource createDynamicDataSource(){Map<Object,Object> dataSourceMap = new HashMap<>();DataSource defaultDataSource = masterDataSource();dataSourceMap.put("master",defaultDataSource);dataSourceMap.put("slave",slaveDataSource());return new DynamicDataSource(defaultDataSource,dataSourceMap);}
}
通過配置類,將配置文件中的配置的數據庫信息轉換成datasource,并添加到DynamicDataSource中,同時通過@Bean將DynamicDataSource注入Spring中進行管理,后期在進行動態數據源添加時,會用到。
5、測試
@GetMapping("/getData.do/{datasourceName}")
public String getMasterData(@PathVariable("datasourceName") String datasourceName){DataSourceContextHolder.setDataSource(datasourceName);TestUser testUser = testUserMapper.selectOne(null);DataSourceContextHolder.removeDataSource();return testUser.getUserName();
}
通過執行結果,我們看到傳遞不同的數據源名稱,查詢對應的數據庫是不一樣的,返回結果也不一樣。
在上述代碼中,我們看到DataSourceContextHolder.setDataSource(datasourceName); 來設置了當前線程需要查詢的數據庫,通過DataSourceContextHolder.removeDataSource(); 來移除當前線程已設置的數據源。
注:啟動程序時,小伙伴不要忘記將SpringBoot自動添加數據源進行排除哦,否則會報循環依賴問題。
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
6、優化調整(注解切換數據源)
在上述中,雖然已經實現了動態切換數據源,但是我們會發現如果涉及到多個業務進行切換數據源的話,我們就需要在每一個實現類中添加這一段代碼。
說到這有小伙伴應該就會想到使用注解來進行優化,接下來我們來實現一下。
6.1 定義注解
我們就用mybatis動態數據源切換的注解:DS,代碼如下:
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DS {String value() default "master";
}
6、2 實現aop
@Aspect
@Component
@Slf4j
public class DSAspect { @Pointcut("@annotation(com.jiashn.dynamic_datasource.dynamic.aop.DS)")public void dynamicDataSource(){}@Around("dynamicDataSource()")public Object datasourceAround(ProceedingJoinPoint point) throws Throwable {MethodSignature signature = (MethodSignature)point.getSignature();Method method = signature.getMethod();DS ds = method.getAnnotation(DS.class);if (Objects.nonNull(ds)){DataSourceContextHolder.setDataSource(ds.value());}try {return point.proceed();} finally {DataSourceContextHolder.removeDataSource();}}
}
代碼使用了@Around,通過ProceedingJoinPoint獲取注解信息,拿到注解傳遞值,然后設置當前線程的數據源
6、4 測試
@GetMapping("/getMasterData.do")
public String getMasterData(){TestUser testUser = testUserMapper.selectOne(null);return testUser.getUserName();
}
@GetMapping("/getSlaveData.do")
@DS("slave")
public String getSlaveData(){TestUser testUser = testUserMapper.selectOne(null);return testUser.getUserName();
}
注意也可以同時操作多個數據源,只需要在相應方法上添加 @DS注解就可以,(service)
由于@DS中設置的默認值是:master,因此在調用主數據源時,可以不用進行添加。
通過執行結果,我們通過@DS也進行了數據源的切換,實現了Mybatis動態切換數據源中的通過注解切換數據源的方式。
第二種方式使用第三方框架,dynamic-datasource多數源組件
<dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.6.1</version></dependency>
配置數據源
spring:application:name: DynamicSourceDatadatasource:dynamic:#??????????????,?????masterprimary: master#???????,??false. true?????????????,false???????strict: falsedatasource:master:url: jdbc:mysql://192.168.64.140:3306/datasource1?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=falseusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driverslave_1:url: jdbc:mysql://192.168.64.136:3306/datasource2?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=falseusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Drivercache:type: redisdata:redis:host: localhostport: 6379#password: 123456database: 0 #????0????jedis:#Redis?????pool:max-active: 8 #?????max-wait: 1ms #???????????max-idle: 4 #???????????min-idle: 0 #???????????
mybatis:mapper-locations: mappers/**/*.xmlconfiguration:map-underscore-to-camel-case: truelog-impl: org.apache.ibatis.logging.stdout.StdOutImpl
測試使用
@RestController
@RequestMapping("/friend")
public class FriendController {@Autowiredprivate FriendService friendService;// 主庫查詢@PostMapping("/find")public List<Friend> getListDatas() {return friendService.findListDatas();}/*** 注意:@DS("dsName") dsName可以為組名也可以為具體某個庫的名稱* 沒有@DS 默認數據源* 3.使用 @DS 切換數據源。* @DS 可以注解在方法上或類上,同時存在就近原則 方法上注解 優先于 類上注解* @return*/// 操作從庫@DS("slave_1")@PostMapping("/find1")public List<Friend> getListDatas1() {return friendService.findListDatas();}// 主從庫同時操作@PostMapping("/find2")@DSTransactionalpublic List<Friend> getListDatas2() {
// 操作主庫數據List<Friend> masters = friendService.findMastDatas();
// 操作從庫數據List<Friend> salves = friendService.findSlaveDatas();masters.addAll(salves);System.out.println(masters);return masters;}
}