前言
本博客姊妹篇
一、功能描述
- 配置方式:配置文件中实现多数据源,非动态
- 使用方式:使用注解切换数据源
二、代码实现
2.1 配置
# spring配置
spring:
# 数据源配置
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: '*.js,*.css,*.gif,*.png,*.jpg,*.ico,/druid/*'
stat-view-servlet:
enabled: true
url-pattern: /druid/*
login-username: admin
login-password: 123456
filter:
stat:
enabled: true
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
enabled: true
config:
multi-statement-allow: true
master:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/boot_business?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8&useSSL=false
username: root
password: root
initial-size: 10
min-idle: 10
max-active: 100
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: select 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
slave:
enabled: true
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/boot_codegen?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8&useSSL=false
username: root
password: root
initial-size: 10
min-idle: 10
max-active: 100
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: select 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
2.2 配置类
package com.qiangesoft.datasource.core;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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 java.util.HashMap;
import java.util.Map;
/**
* 多数据源配置
*
* @author qiangesoft
* @date 2024-03-14
*/
@Slf4j
@Configuration
public class DataSourceConfiguration {
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DruidDataSource masterDataSource() {
DruidDataSource masterDataSource = DruidDataSourceBuilder.create().build();
masterDataSource.setName(DataSourceType.MASTER.getType());
return masterDataSource;
}
@Bean
@ConfigurationProperties("spring.datasource.druid.slave")
@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
public DruidDataSource slaveDataSource() {
DruidDataSource slaveDataSource = DruidDataSourceBuilder.create().build();
slaveDataSource.setName(DataSourceType.SLAVE.getType());
return slaveDataSource;
}
@Bean
@Primary
public DynamicDataSource dynamicDataSource(DruidDataSource masterDataSource, DruidDataSource slaveDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(masterDataSource.getName(), masterDataSource);
targetDataSources.put(slaveDataSource.getName(), slaveDataSource);
return new DynamicDataSource(masterDataSource, targetDataSources);
}
}
2.3 多数据源扩展实现
package com.qiangesoft.datasource.core;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.Map;
/**
* 动态数据源
*
* @author qiangesoft
* @date 2024-03-14
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContext.getDataSource();
}
}
2.4 切面
package com.qiangesoft.datasource.core;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
* 多数据源处理
*
* @author qiangesoft
* @date 2024-03-14
*/
@Slf4j
@Order(1)
@Aspect
@Component
public class DataSourceAspect {
/**
* 切点
*/
@Pointcut("@annotation(com.qiangesoft.datasource.core.DataSource)")
public void pointCut() {
}
/**
* 通知
*
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
DataSource dataSource = this.getDataSource(joinPoint);
if (dataSource == null) {
DataSourceContext.setDataSource(DataSourceType.MASTER);
} else {
DataSourceContext.setDataSource(dataSource.value());
}
try {
return joinPoint.proceed();
} finally {
DataSourceContext.removeDataSource();
}
}
/**
* 获取数据源
*
* @param joinPoint
* @return
*/
public DataSource getDataSource(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 方法上查找注解
DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
if (Objects.nonNull(dataSource)) {
return dataSource;
}
// 类上查找注解
return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
}
}
2.5 线程本地变量
package com.qiangesoft.datasource.core;
/**
* 数据源上下文
*
* @author qiangesoft
* @date 2024-03-14
*/
public class DataSourceContext {
/**
* 线程本地变量:数据源
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置数据源的变量
*/
public static void setDataSource(DataSourceType dataSourceType) {
CONTEXT_HOLDER.set(dataSourceType.getType());
}
/**
* 获得数据源的变量
*/
public static String getDataSource() {
return CONTEXT_HOLDER.get();
}
/**
* 清空数据源变量
*/
public static void removeDataSource() {
CONTEXT_HOLDER.remove();
}
}
2.6 使用
package com.qiangesoft.datasource.controller;
import com.qiangesoft.datasource.core.DataSource;
import com.qiangesoft.datasource.core.DataSourceType;
import com.qiangesoft.datasource.entity.SysUser;
import com.qiangesoft.datasource.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* <p>
* 用户信息 前端控制器
* </p>
*
* @author qiangesoft
* @since 2024-03-14
*/
@RestController
@RequestMapping("/sys/user")
public class SysUserController {
@Autowired
private ISysUserService sysUserService;
@DataSource(DataSourceType.MASTER)
@GetMapping("/master")
public List<SysUser> listMaster() {
return sysUserService.list();
}
@DataSource(DataSourceType.SLAVE)
@GetMapping("/slave")
public List<SysUser> listSlave() {
return sysUserService.list();
}
}