看到網上有不少實現,但是大多講的不仔細,或實現的不優雅這裏記錄一下我的實現方式。
實現思路
- 利用springboot配置多個數據源
- 配置默認數據源,編寫數據源切換類
- 創建切面實現自動切換
UML類圖
如下是實現該功能的類圖
具體實現
多數據源的實現
- 導入依賴
<dependencies>
<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>2.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--mime-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.55</version>
</dependency>
<!--支持熱更新-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
<!--使用log4j2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
</dependencies>
2.創建多數據源bean的配置類
/**
* 多數據源bean的配置類
* @author hy
*/
@Configuration
public class MultipleDataSourceConfig {
@Bean("master")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource createMasterDataSource(){
return new DruidDataSource();
}
@Bean("slave1")
@ConfigurationProperties(prefix = "spring.datasource.slave1")
public DataSource createSlave1DataSource(){
return new DruidDataSource();
}
/**
* 設置動態數據源,通過@Primary 來確定主DataSource
* @return
*/
@Bean
@Primary
public DataSource createDynamicDataSource(@Qualifier("master") DataSource master, @Qualifier("slave1") DataSource slave1){
DynamicDataSource dynamicDataSource = new DynamicDataSource();
//設置默認數據源
dynamicDataSource.setDefaultTargetDataSource(master);
//配置多數據源
Map<Object, Object> map = new HashMap<>();
map.put("master",master);
map.put("slave1",slave1);
dynamicDataSource.setTargetDataSources(map);
return dynamicDataSource;
}
}
這裏是使用配置類注入bean
使用註解@Configuration和ConfigurationProperties,bean的屬性值由配置文件的 spring.datasource.master和spring.datasource.slave1決定
bean的類型是Datasource,名字是由註解@Bean 指定
隨後在方法createDynamicDataSource中使用 @Qualifier 使用指定名字的
- 重寫determineCurrentLookupKey() 方法
public class DynamicDataSource extends AbstractRoutingDataSource {
Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);
@Override
protected Object determineCurrentLookupKey() {
logger.info("------------------當前數據源 {}", DynamicDataSourceSwitcher.getDataSource());
return DynamicDataSourceSwitcher.getDataSource();
}
}
AbstractRoutingDataSource 是spring jdbc提供的操作讀數據源的抽象類,重寫determineCurrentLookupKey() 指定獲得當前數據源
查看父類AbstractRoutingDataSource 的源碼
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
上面是determineTargetDataSource方法,可以看到若determineCurrentLookupKey返回空則使用默認數據源
- 實現操作數據源的類
/**
* 操作數據源
* @author hy
*/
public class DynamicDataSourceSwitcher {
static Logger logger = LoggerFactory.getLogger(DynamicDataSourceSwitcher.class);
public static final String Mater = "master";
public static final String Slave1 = "slave1";
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSource(String name){
logger.info("-------- 設置數據源數據源爲 :{} ", name);
contextHolder.set(name);
}
public static String getDataSource(){
return contextHolder.get();
}
public static void cleanDataSource(){
contextHolder.remove();
}
}
利用ThreadLocal 將數據源與當前線程綁定,並提供get set方法
5.配置application.yml文件
spring:
profiles: multi
datasource:
type: com.alibaba.druid.pool.DruidDataSource
master:
url: jdbc:mysql://xxx:3306/xxx?useSSL=false&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&failOverReadOnly=false
username: xxx
password: xxx
type: com.alibaba.druid.pool.DruidDataSource
name: master
initialize: true
# 監控統計攔截的filters 有stat,wall,log4j
filters: stat
slave1:
url: jdbc:mysql://xxx:3306/xxx?useSSL=false&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&failOverReadOnly=false
username: xxx
password: xxx
type: com.alibaba.druid.pool.DruidDataSource
name: slave1
initialize: true
filters: stat
以上就已經實現了多數據源的配置,要實現自動切換還需要加入aop切面
加入aop切面
- 自定義數據源的註解
/**
* 自定義的數據源的註解
* @author hy
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.METHOD
})
public @interface MyDataSource {
String value() default "master";
}
- 創建切面
/**
* 創建aop切面
* @author hy
*/
@Aspect
@Component
@Order(1) //需要加入切面排序
public class DynamicDataSourceAspect {
private Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
/**
* 切入點只對@Service註解的類上的@DataSource方法生效
* @param myDataSource
*/
@Pointcut(value="@within(org.springframework.stereotype.Service) && @annotation(myDataSource)" )
public void dynamicDataSourcePointCut(MyDataSource myDataSource){}
@Before(value = "dynamicDataSourcePointCut(myDataSource)")
public void switchDataSource(MyDataSource myDataSource) {
DynamicDataSourceSwitcher.setDataSource(myDataSource.value());
}
/**
* 切點執行完後 切換成主數據庫
* @param myDataSource
*/
@After(value="dynamicDataSourcePointCut(myDataSource)")
public void after(MyDataSource myDataSource){
DynamicDataSourceSwitcher.cleanDataSource();
}
}
使用:
@Service
public class WxUserService {
@Resource
WxUserMapper wxUserMapper;
public WxUser getUserById(Integer id){
return wxUserMapper.selectByPrimaryKey(id);
}
@MyDataSource(value = DynamicDataSourceSwitcher.Slave1)
public WxUser getUserByIdWithSlave(Integer id) {
return wxUserMapper.selectByPrimaryKey(id);
}
}
效果如圖: