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

在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));
    }
动态数据源切换可以通过AOP(面向切面编程)和ThreadLocal来实现。其中,ThreadLocal是Java中的一个线程级别的变量,可以在同一个线程中共享数据。自定义注解可以用来标记需要切换数据源的方法。 实现步骤如下: 1. 定义数据源切换注解 ```java @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface DataSource { String value() default "primary"; } ``` 2. 定义数据源切换切面 ```java @Aspect @Component public class DataSourceAspect { @Pointcut("@annotation(com.example.demo.annotation.DataSource)") public void dataSourcePointCut() { } @Before("dataSourcePointCut()") public void before(JoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); DataSource dataSource = signature.getMethod().getAnnotation(DataSource.class); if (dataSource != null) { DynamicDataSourceContextHolder.setDataSource(dataSource.value()); } else { DynamicDataSourceContextHolder.setDataSource("primary"); } } @After("dataSourcePointCut()") public void after(JoinPoint joinPoint) { DynamicDataSourceContextHolder.clearDataSource(); } } ``` 3. 定义ThreadLocal存储数据源信息 ```java public class DynamicDataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); public static void setDataSource(String dataSource) { contextHolder.set(dataSource); } public static String getDataSource() { return contextHolder.get(); } public static void clearDataSource() { contextHolder.remove(); } } ``` 4. 在数据源配置文件中定义多个数据源 ```java @Configuration public class DataSourceConfig { @Bean @ConfigurationProperties("datasource.primary") public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); } @Bean @ConfigurationProperties("datasource.secondary") public DataSource secondaryDataSource() { return DataSourceBuilder.create().build(); } @Bean @Primary public DynamicDataSource dataSource() { DynamicDataSource dynamicDataSource = new DynamicDataSource(); Map<Object, Object> dataSourceMap = new HashMap<>(2); dataSourceMap.put("primary", primaryDataSource()); dataSourceMap.put("secondary", secondaryDataSource()); dynamicDataSource.setTargetDataSources(dataSourceMap); dynamicDataSource.setDefaultTargetDataSource(primaryDataSource()); return dynamicDataSource; } } ``` 5. 在需要切换数据源的方法上添加@DataSource注解 ```java @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override @DataSource("primary") public List<User> listPrimaryUsers() { return userMapper.listUsers(); } @Override @DataSource("secondary") public List<User> listSecondaryUsers() { return userMapper.listUsers(); } } ``` 这样,在调用listPrimaryUsers和listSecondaryUsers方法时,就会自动切换到对应的数据源
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值