背景:主從架構(gòu)下,數(shù)據(jù)庫的讀寫分離
1. 依賴
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</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>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
</dependencies>
2.配置數(shù)據(jù)源
spring:
datasource:
druid:
type: com.alibaba.druid.pool.DruidDataSource
master:
url: jdbc:mysql://127.0.0.1:3307/user1?useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
slave1:
enabled: true
url: jdbc:mysql://127.0.0.1:3308/user1?useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
otmstariff:
enabled: false
url: jdbc:mysql://127.0.0.1:3306/user1?useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
3. 注冊數(shù)據(jù)源
1)創(chuàng)建一個數(shù)據(jù)源枚舉類
public enum DataSourceType {
/**
* 主庫
*/
MASTER,
/**
* 從庫
*/
SLAVE1,
SLAVE2
}
2)我們切換數(shù)據(jù)庫所需要的bean全部交給spring容器中
@Configuration
public class DynamicDataSourceConfig {
@Bean
@Qualifier("masterDataSource")
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Qualifier("slave1DataSource")
@Bean
@ConfigurationProperties("spring.datasource.druid.slave1")
// 根據(jù)配置文件enabled屬性,判斷該配置是否生效
@ConditionalOnProperty(prefix = "spring.datasource.druid.slave1", name = "enabled", havingValue = "true")
public DataSource slave1DataSource() {
return DruidDataSourceBuilder.create().build();
}
@Qualifier("slave2DataSource")
@Bean
@ConfigurationProperties("spring.datasource.druid.slave2")
@ConditionalOnProperty(prefix = "spring.datasource.druid.slave2", name = "enabled", havingValue = "true")
public DataSource slave2DataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource dataSource(DataSource masterDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
setDataSource(targetDataSources, DataSourceType.SLAVE1.name(), "slave1DataSource");
setDataSource(targetDataSources, DataSourceType.SLAVE2.name(), "slave2DataSource");
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
return dynamicDataSource;
}
/**
* 設(shè)置數(shù)據(jù)源
*
* @param targetDataSources 備選數(shù)據(jù)源集合
* @param sourceName 數(shù)據(jù)源名稱
* @param beanName bean名稱
*/
public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName)
{
try
{
DataSource dataSource = SpringUtils.getBean(beanName);
targetDataSources.put(sourceName, dataSource);
}
catch (Exception e)
{
}
}
}
4.切換數(shù)據(jù)源
public class DynamicDataSourceContextHolder {
// 線程安全
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 設(shè)置數(shù)據(jù)源變量
* @param dataSourceEnum 數(shù)據(jù)源變量
*/
public static void setDataSourceType(String type) {
CONTEXT_HOLDER.set(type);
}
/**
* 獲取數(shù)據(jù)源變量
* @return 數(shù)據(jù)源變量
*/
public static String getDataSourceType() {
return CONTEXT_HOLDER.get();
}
/**
* 清理數(shù)據(jù)源
* @return 數(shù)據(jù)源變量
*/
public static void clearDataSourceType() {
CONTEXT_HOLDER.remove();
}
}
5.設(shè)置數(shù)據(jù)源
新建DynamicDataSource類繼承AbstractRoutingDataSource類,并實現(xiàn)determineCurrentLookupKey方法,該方法是指定當(dāng)前默認(rèn)數(shù)據(jù)源的方法,該類是實現(xiàn)動態(tài)切換數(shù)據(jù)源的關(guān)鍵
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
6. 自定義多數(shù)據(jù)源切換注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
DataSourceType value() default DataSourceType.MASTER;
}
7. AOP攔截器的實現(xiàn)
@Slf4j
@Aspect
@Component
public class DataSourceAspect {
@Pointcut("@annotation(com.example.datasourceprimordialdemo2.datasource.annotation.DataSource)")
public void doPointCut() {
}
@Around("doPointCut()")
public Object around(ProceedingJoinPoint pointcut) throws Throwable {
MethodSignature signature = (MethodSignature) pointcut.getSignature();
Method method = signature.getMethod();
DataSource dataSource = method.getAnnotation(DataSource.class);
if (Objects.nonNull(dataSource)) {
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
}
try {
return pointcut.proceed();
} finally {
// 銷毀數(shù)據(jù)源 在執(zhí)行方法之后
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
}
8. 啟動類修改
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
9. 使用
此處為了測試,直接放在controller使用
@GetMapping("/0")
@DataSource(DataSourceType.MASTER)
public ResponseEntity<List<User>> query() {
return ResponseEntity.ok(this.userService.query());
}
@GetMapping("/1")
@DataSource(DataSourceType.SLAVE1)
public ResponseEntity<List<User>> query2() {
return ResponseEntity.ok(this.userService.query());
}
@GetMapping("/2")
@DataSource(DataSourceType.SLAVE2)
public ResponseEntity<List<User>> query3() {
return ResponseEntity.ok(this.userService.query());
}
為了區(qū)分?jǐn)?shù)據(jù)不一樣,數(shù)據(jù)庫未做主從同步
master的數(shù)據(jù)

在這里插入圖片描述
slave1的數(shù)據(jù)

在這里插入圖片描述
slave2的數(shù)據(jù)

在這里插入圖片描述
5.用postman進(jìn)行測試
獲取master的數(shù)據(jù)

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-N4UsMMk8-1677832353467)(img/img_3.png)]
獲取slave1的數(shù)據(jù)

在這里插入圖片描述
獲取slave2的數(shù)據(jù)

在這里插入圖片描述
完成~~~ 源碼: gitee github
注: 為什么配置了三個數(shù)據(jù)源,是為了展示我在做的過程中遇到的一個問題
- 剛開始我是按照網(wǎng)上查到的方式注冊數(shù)據(jù)源

在這里插入圖片描述
- 這樣做,在全部數(shù)據(jù)源都注入的時候沒有問題,當(dāng)我在配置中心停掉其中一個數(shù)據(jù)源時就會出現(xiàn)問題

在這里插入圖片描述
3)問題(在masterDataSource添加@Primary又會出現(xiàn)其他的錯誤)
Parameter 1 of method dataSource in com.example.datasourceprimordialdemo2.datasource.config.DynamicDataSourceConfig required a single bean, but 2 were found:
- masterDataSource: defined by method 'masterDataSource' in class path resource [com/example/datasourceprimordialdemo2/datasource/config/DynamicDataSourceConfig.class]
- slave2DataSource: defined by method 'slave2DataSource' in class path resource [com/example/datasourceprimordialdemo2/datasource/config/DynamicDataSourceConfig.class]
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

在這里插入圖片描述
4.解決辦法(此方式只針對我代碼的解決辦法,可能會有其他問題導(dǎo)致 此報錯,請再尋找其他方法)

在這里插入圖片描述
- 大佬們?nèi)绻衅渌绞秸堅谠u論區(qū)告知,萬分感謝
我是Tz ,想把我遇到的問題都分享給你,想看更多精彩內(nèi)容,請關(guān)注我的wx公眾號zhuangtian
本文由博客一文多發(fā)平臺 OpenWrite 發(fā)布!