在Java开发中,使用多数据源能够提高系统的灵活性和性能。本文将通过介绍自定义注解的方式,实现动态数据源的切换。通过这种创新性的方法,开发者可以根据业务需求轻松切换数据库连接,实现数据源的动态管理,提升系统的可扩展性和响应性。通过深入了解自定义注解的原理,读者将能够更好地利用这一特性,优化数据库访问策略,提高应用程序的整体性能。
怎么通过自定义注解和面向切面的方式结合实现动态切换数据源。
代码实践,controller层根据id获取用户信息
@RestController
public class UserController {
@Resource
private UserService userService;
@GetMapping("/v1/user/{id}")
@UsingDataSource("ds1")
public User getById1(@PathVariable String id) {
return userService.getByUserId1(id);
}
@GetMapping("/v2/user/{id}")
public User getById2(@PathVariable String id) {
return userService.getByUserId2(id);
}
}
service层上注解一个是数据源1,一个是数据源2
public interface UserService {
@UsingDataSource("ds1")
User getByUserId1(String userId);
@UsingDataSource("ds2")
User getByUserId2(String userId);
}
在spring框架中要实现动态数据源一个核心的类就是AbstractRoutingDataSource
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getKey();
}
}
同时把他注册到IOC容器中
//省略代码。。
@Bean
public DynamicDataSource dynamicDataSource() {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("ds1", dataSource1());
targetDataSources.put("ds2", dataSource2());
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.setDefaultTargetDataSource(dataSource1());
return dynamicDataSource;
}
@ConfigurationProperties("datasource1")
//数据源1
@Bean
public DataSource dataSource1() {
return DataSourceBuilder.create().build();
}
//数据源2
@Bean
@ConfigurationProperties("datasource2")
public DataSource dataSource2() {
return DataSourceBuilder.create().build();
}
//省略代码。。
使用上下文的容器存放这个key,他是线程安全的
public class DataSourceContextHolder {
public static ThreadLocal<String> key = new ThreadLocal<>();
public static void setKey(String key) {
DataSourceContextHolder.key.set(key);
}
public static String getKey() {
return key.get();
}
//每次使用完都要清空掉,线程绑定的key
public static void clearKey() {
key.remove();
}
}
现在我们了解了想要动态切换数据源,在调用查找数据库之前设置这个key值,这样就可以使spring使用动态数据源的实现类根据key找到对应的数据库信息。
想要实现动态数据源的话,需要自己实现sqlSessionFactoryBean
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DynamicDataSource dynamicDataSource) throws IOException {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
/** 设置mybatis configuration 扫描路径 */
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
bean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));//加载配置文件的地址;//
//自己实现的动态数据源
bean.setDataSource(dynamicDataSource);
return bean;
}
//事务管理器也设置自定义的数据源
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
切面类的实现
@Aspect
@Component
public class DataSourceAspect {
//以自定义注解的方式切入,在加了这个注解的方法上切入
@Pointcut("@annotation(com.example.demo.datasource.UsingDataSource)")
public void checkPointCut() {
}
//在执行方法之前切入
@Before("checkPointCut()")
public void checkBefore(JoinPoint joinPoint) {
try {
//获取切入方法上的注解
Class<?> clazz = joinPoint.getTarget().getClass();
String methodName = joinPoint.getSignature().getName();
Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
Method method = clazz.getMethod(methodName, parameterTypes);
UsingDataSource usingDataSource = method.getAnnotation(UsingDataSource.class);
//给上下文容器中设置key
String dataSourceKey = usingDataSource.value();
DataSourceContextHolder.setKey(dataSourceKey);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
//方法调用之后清理上下文中的key
@After("checkPointCut()")
public void checkAfter(){
DataSourceContextHolder.clearKey();
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface UsingDataSource {
String value() default "";
}
整个过程结束
一些情况会导致动态代理失效,也就会导致aop失效,比如在service层中调用getByUserId,使用的数据源不是ds2
//省略部分代码
@Override
public User getByUserId(String userId) {
return getByUserId2(userId);
}
@Override
@UsingDataSource("ds2")
public User getByUserId2(String userId) {
return userDao.getById(Integer.parseInt(userId));
}
如果想要代理不失效,可以获取当前代理对象,然后通过该代理对象调用了 getByUserId2 方法,同时需要暴露代理类,在启动类上配置@EnableAspectJAutoProxy(exposeProxy = true)
@Override
public User getByUserId(String userId) {
UserService o = (UserService) AopContext.currentProxy();
return o.getByUserId2(userId);
}
@Override
@UsingDataSource("ds2")
public User getByUserId2(String userId) {
return userDao.getById(Integer.parseInt(userId));
}