原文鏈接: Spring Boot2.x 動態數據源配置
基於 Spring Boot 2.x、Spring Data JPA、druid、mysql 的動態數據源配置Demo,適合用於數據庫的讀寫分離等應用場景。通過在Service層方法上添加自定義註解實現讀寫不同的數據庫。
配置文件已配置好druid監控相關屬性,監控頁面鏈接:ip:8080/druid。賬號:admin,密碼:123456。詳情查看 application.yml 文件。
配置 pom.xml 文件
<dependencies>
<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.1.21</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
配置application.yml文件
spring:
datasource:
druid:
primary:
driverClassName: com.mysql.cj.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/primary?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
filters: stat,wall
local:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/local?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
filters: stat,wall
stat-view-servlet:
enabled: true
login-username: admin
login-password: 123456
reset-enable: false
url-pattern: /druid/*
web-stat-filter:
enabled: true
# 添加過濾規則
url-pattern: /*
# 忽略過濾格式
exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
jpa:
database: MYSQL
hibernate:
show_sql: true
format_sql: true
primary-dialect: org.hibernate.dialect.MySQL5InnoDBDialect
secondary-dialect: org.hibernate.dialect.MySQL5InnoDBDialect
# 打開後會自動在主庫生成表
# ddl-auto: update
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
# 打開後會自動在主庫生成表
# generate-ddl: true
項目目錄結構
DataSource.java
package dynamic.data.annotation;
import dynamic.data.common.ContextConst;
import java.lang.annotation.*;
/**
* @Author: ChangXuan
* @Decription:
* @Date: 22:25 2020/2/23
**/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
ContextConst.DataSourceType value() default ContextConst.DataSourceType.PRIMARY;
}
DynamicDataSourceAspect.java
package dynamic.data.aspect;
import dynamic.data.common.ContextConst;
import dynamic.data.datasource.DataSourceContextHolder;
import dynamic.data.annotation.DataSource;
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;
/**
* @Author: ChangXuan
* @Decription:
* @Date: 22:28 2020/2/23
**/
@Component
@Aspect
public class DynamicDataSourceAspect {
@Before("execution(* dynamic.data.service..*.*(..))")
public void before(JoinPoint point){
try {
DataSource annotationOfClass = point.getTarget().getClass().getAnnotation(DataSource.class);
String methodName = point.getSignature().getName();
Class[] parameterTypes = ((MethodSignature) point.getSignature()).getParameterTypes();
Method method = point.getTarget().getClass().getMethod(methodName, parameterTypes);
DataSource methodAnnotation = method.getAnnotation(DataSource.class);
methodAnnotation = methodAnnotation == null ? annotationOfClass:methodAnnotation;
ContextConst.DataSourceType dataSourceType = methodAnnotation != null && methodAnnotation.value() !=null ? methodAnnotation.value() :ContextConst.DataSourceType.PRIMARY ;
DataSourceContextHolder.setDataSource(dataSourceType.name());
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
@After("execution(* dynamic.data.service..*.*(..))")
public void after(JoinPoint point){
DataSourceContextHolder.clearDataSource();
}
}
ContextConst.java
package dynamic.data.common;
/**
* @Author: ChangXuan
* @Decription:
* @Date: 22:17 2020/2/23
**/
public interface ContextConst {
enum DataSourceType{
PRIMARY,LOCAL
}
}
DataSourceContextHolder .java
package dynamic.data.datasource;
/**
* @Author: ChangXuan
* @Decription:
* @Date: 22:23 2020/2/23
**/
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setDataSource(String dbType){
System.out.println("切換到["+dbType+"]數據源");
contextHolder.set(dbType);
}
public static String getDataSource(){
return contextHolder.get();
}
public static void clearDataSource(){
contextHolder.remove();
}
}
DynamicDataSource.java
package dynamic.data.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* @Author: ChangXuan
* @Decription:
* @Date: 22:22 2020/2/23
**/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
MutiplyDataSource.java
package dynamic.data.datasource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import dynamic.data.common.ContextConst;
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 org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
/**
* @Author: ChangXuan
* @Decription:
* @Date: 22:15 2020/2/23
**/
@Configuration
public class MutiplyDataSource {
@Bean(name = "dataSourcePrimary")
@ConfigurationProperties(prefix = "spring.datasource.druid.primary")
public DataSource primaryDataSource(){
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "dataSourceLocal")
@ConfigurationProperties(prefix = "spring.datasource.druid.local")
public DataSource localDataSource(){
return DruidDataSourceBuilder.create().build();
}
@Primary
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
//配置默認數據源
dynamicDataSource.setDefaultTargetDataSource(primaryDataSource());
//配置多數據源
HashMap<Object, Object> dataSourceMap = new HashMap();
dataSourceMap.put(ContextConst.DataSourceType.PRIMARY.name(),primaryDataSource());
dataSourceMap.put(ContextConst.DataSourceType.LOCAL.name(),localDataSource());
dynamicDataSource.setTargetDataSources(dataSourceMap);
return dynamicDataSource;
}
/**
* 配置@Transactional註解事務
* @return
*/
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
}
使用
在 DynamicDataSourceAspect.java 中配置的service下使用註解的方式指定執行的方法使用哪個數據庫。示例參考下方代碼: