在Java开发中,使用多数据源能够提高系统的灵活性和性能。本文将通过介绍自定义注解的方式,实现动态数据源的切换。通过这种创新性的方法,开发者可以根据业务需求轻松切换数据库连接,实现数据源的动态管理,提升系统的可扩展性和响应性。通过深入了解自定义注解的原理,读者将能够更好地利用这一特性,优化数据库访问策略,提高应用程序的整体性能。
怎么通过自定义注解和面向切面的方式结合实现动态切换数据源。
代码实践,controller层根据id获取用户信息
@RestController
public class UserController {@Resourceprivate 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 {@Overrideprotected Object determineCurrentLookupKey() {return DataSourceContextHolder.getKey();}
}
同时把他注册到IOC容器中
//省略代码。。
@Beanpublic 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@Beanpublic 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();}//每次使用完都要清空掉,线程绑定的keypublic static void clearKey() {key.remove();}
}
现在我们了解了想要动态切换数据源,在调用查找数据库之前设置这个key值,这样就可以使spring使用动态数据源的实现类根据key找到对应的数据库信息。
想要实现动态数据源的话,需要自己实现sqlSessionFactoryBean
@Beanpublic 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;}//事务管理器也设置自定义的数据源@Beanpublic 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);//给上下文容器中设置keyString 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
//省略部分代码@Overridepublic 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)
@Overridepublic 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));}