目录
- 一、pom依赖
- 二、yml配置文件
- 三、自定义切换数据源注解
- 四、定义AOP切面切换数据源
- 五、读取Druid连接池配置
- 六、Druid多数据源配置类
- 七、配置动态数据源
- 八、操作数据源类
- 九、数据源类型枚举类
- 总结
- 源码
流程解释:
一、pom依赖
<!-- druid连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.4</version>
</dependency>
二、yml配置文件
spring:
datasource:
#####RuoYi多数据源配置#####
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
url: jdbc:mysql://localhost:3306/day27?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 123456
# 从库数据源
slave:
# 从数据源开关/默认关闭 false/true
enabled: true
url: jdbc:mysql://localhost:3306/quartz?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 123456
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
三、自定义切换数据源注解
/**
* @author :LiuShihao
* @date :Created in 2021/5/20 10:04 上午
* @desc :自定义多数据源切换注解
* 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource
{
/**
* 切换数据源名称
*/
public DataSourceType value() default DataSourceType.MASTER;
}
四、定义AOP切面切换数据源
/**
* 多数据源处理
* 注解@Order或者接口Ordered的作用是定义Spring IOC容器中Bean的执行顺序的优先级,
* 而不是定义Bean的加载顺序,Bean的加载顺序不受@Order或Ordered接口的影响;
* 默认是最低优先级,值越小优先级越高
* @author ruoyi
*/
@Aspect
@Order(1)
@Component
public class DataSourceAspect {
protected Logger logger = LoggerFactory.getLogger(getClass());
@Pointcut("@annotation(com.lsh.anno.DataSource) || @within(com.lsh.anno.DataSource)")
public void dsPointCut() {
}
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
DataSource dataSource = getDataSource(point);
if (dataSource != null) {
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
}
try {
return point.proceed();
}
finally {
// 销毁数据源 在执行方法之后
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
/**
* 获取需要切换的数据源
*/
public DataSource getDataSource(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
if (Objects.nonNull(dataSource)) {
return dataSource;
}
return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
}
}
五、读取Druid连接池配置
/**
* @author :LiuShihao
* @date :Created in 2021/5/20 10:31 上午
* @desc :Druid连接池配置
*/
@Configuration
public class DruidProperties
{
@Value("${spring.datasource.druid.initialSize}")
private int initialSize;
@Value("${spring.datasource.druid.minIdle}")
private int minIdle;
@Value("${spring.datasource.druid.maxActive}")
private int maxActive;
@Value("${spring.datasource.druid.maxWait}")
private int maxWait;
@Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
private int timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")
private int minEvictableIdleTimeMillis;
@Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}")
private int maxEvictableIdleTimeMillis;
@Value("${spring.datasource.druid.validationQuery}")
private String validationQuery;
@Value("${spring.datasource.druid.testWhileIdle}")
private boolean testWhileIdle;
@Value("${spring.datasource.druid.testOnBorrow}")
private boolean testOnBorrow;
@Value("${spring.datasource.druid.testOnReturn}")
private boolean testOnReturn;
/**
* 初始化Druid数据源
* @param datasource
* @return
*/
public DruidDataSource dataSource(DruidDataSource datasource) {
/** 配置初始化大小、最小、最大 */
datasource.setInitialSize(initialSize);
datasource.setMaxActive(maxActive);
datasource.setMinIdle(minIdle);
/** 配置获取连接等待超时的时间 */
datasource.setMaxWait(maxWait);
/** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
/** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
/**
* 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
*/
datasource.setValidationQuery(validationQuery);
/** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */
datasource.setTestWhileIdle(testWhileIdle);
/** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
datasource.setTestOnBorrow(testOnBorrow);
/** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
datasource.setTestOnReturn(testOnReturn);
return datasource;
}
}
六、Druid多数据源配置类
/**
* druid 配置多数据源
* @Date 2021年05月20日10:30:46
* @author ruoyi
*
* 需要在启动类排除DataSource的自动配置类,否则会出现循环引用的问题
*
* org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration
* ┌─────┐
* | dynamicDataSource defined in class path resource [com/lsh/config/DruidConfig.class]
* ↑ ↓
* | masterDataSource defined in class path resource [com/lsh/config/DruidConfig.class]
* ↑ ↓
* | org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker
* └─────┘
*/
@Configuration
public class DruidConfig{
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource(DruidProperties druidProperties) {
System.out.println("注入主数据源");
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
return druidProperties.dataSource(dataSource);
}
@Bean
@ConfigurationProperties("spring.datasource.druid.slave")
@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
public DataSource slaveDataSource(DruidProperties druidProperties) {
System.out.println("注入从数据源");
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
return druidProperties.dataSource(dataSource);
}
/**
* 改进:直接使用@Qualifier注解指定将两个数据源作为入参
* @param masterDataSource
* @param slaveDataSource
* @return
*/
@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource dataSource(@Qualifier("masterDataSource")DataSource masterDataSource, @Qualifier("slaveDataSource")DataSource slaveDataSource) {
System.out.println("注入动态数据源");
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
targetDataSources.put(DataSourceType.SLAVE.name(), slaveDataSource);
return new DynamicDataSource(masterDataSource, targetDataSources);
}
}
七、配置动态数据源
Spring
的AbstractRoutingDataSource
就是采用这种架构。
重点是determineTargetDataSource
方法:
重点在于determineCurrentLookupKey()
方法,这是AbstractRoutingDataSource
类中的一个抽象方法,而它的返回值是你所要用的数据源dataSource
的key
值,有了这个key
值,resolvedDataSource
(这是个map)就从中取出对应的DataSource
,如果找不到,就用配置默认的数据源。
扩展AbstractRoutingDataSource
类,并重写其中的determineCurrentLookupKey()
方法,来实现数据源的切换。
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
System.out.println("加载动态数据源...");
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
/**
* 重写determineCurrentLookupKey()方法
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
八、操作数据源类
封装一个的对动态数据源进行操作的类:
/**
* 数据源切换处理
* @Date 2021年05月20日10:07:21
* @author LiuShihao
*
* 封装一个的对数据源进行操作的类 DynamicDataSourceContextHolder
*/
@Slf4j
public class DynamicDataSourceContextHolder {
/**
* 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置数据源的变量
* 设置当前线程的线程局部变量的值。
*/
public static void setDataSourceType(String dsType) {
log.info("切换到{}数据源", dsType);
CONTEXT_HOLDER.set(dsType);
}
/**
* 返回当前线程所对应的线程局部变量。
* 获得数据源的变量
*/
public static String getDataSourceType() {
return CONTEXT_HOLDER.get();
}
/**
* 清空数据源变量
* 将当前线程局部变量的值删除,目的是为了减少内存的占用,
* 该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
*/
public static void clearDataSourceType() {
CONTEXT_HOLDER.remove();
}
}
九、数据源类型枚举类
public enum DataSourceType{
/**
* 主库
*/
MASTER,
/**
* 从库
*/
SLAVE
}
总结
使用流程:
- 在yml配置文件配置从库数据源。
- 在
DataSourceType
类添加数据源枚举。 - 在
DruidConfig
配置读取数据源。 - 在
DruidConfig
类dataSource
方法添加数据源。 - 在类或者方法上添加
@DataSource
注解切换数据源。
@DataSource(value = DataSourceType.MASTER)
@Override
public List<User> findAll4() {
return userRepository.findAll();
}
@DataSource(value = DataSourceType.SLAVE)
@Override
public List<AsCustInfo> findAll5() {
return asCustInfoRepository.findAll();
}