主要思想:自定义一个@DataSource注解加在方法或者类上面,表示该方法或者类中的所有方法都使用某一个数据源(注解中的值就是是数据源名称)通过aop使用环绕通知在方法执行前将所使用的数据源名称存入到ThreadLocal中(并在方法执行完成后remove ThreadLocal的值),重新实现AbstractRoutingDataSource类,使其在方法执行期间读取数据源时,自动去ThreadLocal获取数据源名称。(没有注解使用默认数据源)
1.自定义一个注解@DataSource
//这个注解将来可以夹在某一个service类或者方法上,通过value属性来指定类或者方法应该使用哪个数据源
@Retention(RetentionPolicy.RUNTIME)//范围 编译成class文件之后注解仍然存在
@Target({ElementType.TYPE,ElementType.METHOD})//作用域
public @interface DataSource {
//默认数据源名称为master
String value() default DataSourceType.DEFAULT_DS_NAME;
}
public interface DataSourceType {
String DEFAULT_DS_NAME = "master";//默认数据源
}
2.自定义DruidProperties类注入yaml中数据库配置信息
@ConfigurationProperties(prefix = "spring.datasource")
public class DruidProperties {
private String type;
private String driverClassName;
private Map<String,Map<String,String>> druid;
private Integer initialSize;
private Integer minIdle;
private Integer maxActive;
private Integer maxWait;
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
url: jdbc:mysql://localhost:3306/test01?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 120405
# 从库数据源
slave:
url: jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 120405
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
3.加载配置中的所有数据源放入map中
@Component
@EnableConfigurationProperties(DruidProperties.class)
public class LoadDataSource {
@Autowired
DruidProperties druidProperties;
public Map<String, DataSource> loadAllDataSource() throws Exception {
Map<String,DataSource> map = new HashMap<>();
Map<String,Map<String,String>> ds = druidProperties.getDruid();//存放基本属性的map
Set<String> keySet = ds.keySet();
for(String key : keySet){
//设置其余公共属性
map.put(key, druidProperties.dataSource((DruidDataSource) DruidDataSourceFactory.createDataSource(ds.get(key))));
}
return map;
}
}
4.定义切点 切面
@Component
@Aspect
public class DataSourceAspect {
/**
* 切点
* @annotation(org.vds.dynamic_datasource.annotation.DataSource表示方法上有@DataSource注解就将方法拦截下来
* @within(org.vds.dynamic_datasource.annotation.DataSource)表示如果类上面有@DataSource注解就将类中的方法拦截下来
*/
@Pointcut("@annotation(org.vds.dynamic_datasource.annotation.DataSource) || @within(org.vds.dynamic_datasource.annotation.DataSource)")
public void pc(){
}
@Around("pc()")
public Object around(ProceedingJoinPoint pjp){
//获取方法上面的有效注解
DataSource dataSource = getDataSource(pjp);
if(dataSource != null){
String value = dataSource.value();//获取注解上的值
DynamicDataSourceContextHolder.setDataSourceType(value);//存入ThreadLocal
}
try {
return pjp.proceed();//被切入方法继续执行
} catch (Throwable e) {
throw new RuntimeException(e);
}finally {
DynamicDataSourceContextHolder.clearDataSourceType();//remove ThreadLocal中的值
}
}
private DataSource getDataSource(ProceedingJoinPoint pjp) {
MethodSignature signature = (MethodSignature) pjp.getSignature();//获取类的信息
//查找方法上的注解
DataSource annotation = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
if(annotation != null){
//说明方法上有@DataSource注解
return annotation;
}
//查找类上的@DataSsource注解
return AnnotationUtils.findAnnotation(signature.getDeclaringType(),DataSource.class);
}
}
5.自定义DynamicDataSourceContextHolder 将数据源名称放入ThreadLocal
/**
* 这个类用来存储当前线程所使用的数据源名称
*/
public class DynamicDataSourceContextHolder {
private static ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
public static void setDataSourceType(String dsType){
CONTEXT_HOLDER.set(dsType);
}
public static String getDataSourceType(){
return CONTEXT_HOLDER.get();
}
public static void clearDataSourceType(){
CONTEXT_HOLDER.remove();
}
}
6.重新实现AbstractRoutingDataSource类
@Component
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(LoadDataSource loadDataSource) throws Exception {
//1.设置所有的数据源
Map<String, DataSource> stringDataSourceMap = loadDataSource.loadAllDataSource();
super.setTargetDataSources(new HashMap<>(stringDataSourceMap));
//2.设置默认的数据源
//没有@Datasource的注解方法所使用的数据源
super.setDefaultTargetDataSource(stringDataSourceMap.get(DataSourceType.DEFAULT_DS_NAME));
super.afterPropertiesSet();
}
/**
* 这个方法用来返回数据源名称,当系统需要获取数据源的时候,会自动调用该方法获取数据源的名称
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
测试
数据库user表展示
不加注解查询user表
@Service
public class UserService {
@Autowired
UserMapper userMapper;
public List<User> getAllUsers(){
return userMapper.getAllUsers();
}
}
//test
@SpringBootTest
class DynamicDatasourceApplicationTests {
@Autowired
UserService userService;
@Test
void contextLoads() {
List<User> allUsers = userService.getAllUsers();
for(User user : allUsers){
System.out.println(user);
}
}
}
结果
加上注解查询user表
@Service
public class UserService {
@Autowired
UserMapper userMapper;
@DataSource("slave")//加上注解
public List<User> getAllUsers(){
return userMapper.getAllUsers();
}
}
结果
测试成功