前序
因为spring内置了一个AbstractRoutingDataSource,它可以把多个数据源存到一个Map中,根据不同的key获取不同的数据源,从而实现动态切换数据源效果
1.本地创建两个数据库并创建 user 表供测试使用
2.创建工程并配置双数据源
# 配置数据源
spring:
datasource:
dynamic:
primary: learn
datasource:
learn:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql:///learn?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
username: root
password: root
learnslave:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql:///learn_slave?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
username: root
password: root
3.创建controller,service,impl,mapper
@SpringBootApplication
//配置mapper路径
@MapperScan({"com.ziye.mapper"})
public class DynamicDatasourceApplication {
public static void main(String[] args) {
SpringApplication.run(DynamicDatasourceApplication.class, args);
}
}
public interface UserService {
List<User> findAllUser();
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public List<User> findAllUser() {
List<User> list = userMapper.findAllUser();
System.out.println(list.toString());
return list;
}
}
public interface UserMapper {
@Select("select id,username from user")
List<User> findAllUser();
}
4.创建自定义注解类并添加到方法上
//表示该注解可用在方法上
@Target(ElementType.METHOD)
//设置生命周期
@Retention(RetentionPolicy.RUNTIME)
public @interface DynamicDatasource {
//默认为 learn
String value() default "learn";
}
/**
* @Author xuxu
* @Date 2022/6/24
* @Describe 测试动态数据源controller
*/
@RestController
public class TestDynamicDatasourceController {
@Autowired
private UserService userService;
@GetMapping("getLearn")
@DynamicDatasource
public List<User> getLearn() {
return userService.findAllUser();
}
@GetMapping("getLearnSlave")
@DynamicDatasource("learnslave")
public List<User> getLearnSlave(){
return userService.findAllUser();
}
}
5.自定义数据源扫描类并添加到AbstractRoutingDataSource的Map中(targetDataSources,resolvedDataSources)并在启动类上排除spring自身的数据源扫描类
//标注为配置类
@Configuration
public class MyDataSourceAutoConfiguration {
private final Logger log = LoggerFactory.getLogger(MyDataSourceAutoConfiguration.class);
//加入到IoC容器,如果没有key默认为该方法名 -> learnDataSource
@Bean
//此处能扫描到yml中的配置信息
@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.learn")
public DataSource learnDataSource() {
log.info("创建 learn 数据源 ...");
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.learnslave")
public DataSource learnSlaveDataSource() {
log.info("创建 learnslave 数据源 ...");
return DataSourceBuilder.create().build();
}
@Bean
//@Primary 该类返回有多个 DataSource 需要指定一个默认的
@Primary
public DataSource masterDataSource(
//@Qualifier -> 因IoC容器中有多个DataSource ,所以需要通过key再次指定
@Autowired @Qualifier("learnDataSource") DataSource learnDataSource,
@Autowired @Qualifier("learnSlaveDataSource") DataSource learnSlaveDataSource
) {
Map<Object, Object> map = new HashMap<>();
map.put("learn", learnDataSource);
map.put("learnslave", learnSlaveDataSource);
RoutingDataSource routingDataSource = new RoutingDataSource();
routingDataSource.setTargetDataSources(map);
return routingDataSource;
}
}
//排除spring自身的数据源获取类
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
//配置mapper路径
@MapperScan({"com.ziye.mapper"})
public class DynamicDatasourceApplication {
public static void main(String[] args) {
SpringApplication.run(DynamicDatasourceApplication.class, args);
}
}
6.通过源码得知获取连接前执行了determineTargetDataSource()方法,再从determineCurrentLookupKey()方法中,拿到了当前容器的key,通过key从Map中获取到了数据源.
此处由determineCurrentLookupKey()方法得知将数据源的key存储到ThreadLocal中
接下来创建自己的数据源获取类并继承AbstractRoutingDataSource并重写determineCurrentLookupKey()方法
public class RoutingDataSourceContext {
//存储到 ThreadLocal 中
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
public RoutingDataSourceContext(String key) {
threadLocal.set(key);
}
//方便取当前threadLocal中的key
public static String getRoutingDataSourceKey() {
String key = threadLocal.get();
return key == null ? "learn" : key;
}
}
供切面类调用
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
//从 threadLocal 中拿到数据源的key
return RoutingDataSourceContext.getRoutingDataSourceKey();
}
}
7.编写aop
//标注为是一个AOP切面类
@Aspect
//@Component 把类实例化到容器中,相当于配置文件中的 <bean id="" class=""/>
@Component
public class RoutingAspect {
//@Around环绕通知 -> SpringAOP 增强注解
//@Around("execution(* com.ziye.controller.*.*(..))") -> 执行controller包下任意方法时均会进行增强
@Around("@annotation(dynamicDatasource)")
//参数 1: ProceedingJoinPoint -> 正在执行的连接点
//参数 2: 传入的实例注解
public Object routingAspect(ProceedingJoinPoint point, DynamicDatasource dynamicDatasource) throws Throwable {
// 拿到注解上的参数
String key = dynamicDatasource.value();
new RoutingDataSourceContext(key);
//继续执行方法
return point.proceed();
}
}
8.测试
end