自定义注解实现动态数据源

在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));
    }

你可能感兴趣的:(spring实战,java,spring,spring源码,aop,ioc,动态数据源,mybatis)