springboot + druid + Aspectj +Jpa(hibernate) 实现动态数据源切换

springboot + druid + Aspectj +Jpa(hibernate) 实现动态数据源切换

我们开发中经常会遇到连接多个数据库的操作,但目前常用的orm框架mybatis、hibernate默认都是只能连接一个数据库。使用原生JDBC虽然可以连接多个数据库,但是却不能应用各种框架所提供的便利。此处提供一种本菜鸟开发中经常使用的多数据源切换方式。

本文先介绍详细的使用步骤,具体的流程讲解在文末进行描述。

1、导入依赖(此处只贴出了核心依赖)

<!-- postgresql -->
<dependency>
   <groupId>org.postgresql</groupId>
   <artifactId>postgresql</artifactId>
</dependency>
<!-- spring-data-jpa -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!-- hibernate -->
<dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-spatial</artifactId>
   <version>${hibernate.version}</version>
   <exclusions>
      <exclusion>
         <artifactId>jts-core</artifactId>
         <groupId>com.vividsolutions</groupId>
      </exclusion>
   </exclusions>
</dependency>
<!-- druid -->
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>druid-spring-boot-starter</artifactId>
   <version>1.1.17</version>
</dependency>

2、在application.properties中配置多个数据源(此处为了简单,只配置了两个)

spring.datasource.druid.test1.url=jdbc:postgresql://10.194.98.235:7017/testdb1
spring.datasource.druid.test1.username=testdb1_user
spring.datasource.druid.test1.password=Owa6TasO
spring.datasource.druid.test1.driverClassName=org.postgresql.Driver

spring.datasource.druid.test2.url=jdbc:postgresql://10.194.101.37:5432/testdb2
spring.datasource.druid.test2.username=postgres
spring.datasource.druid.test2.password=postgres
spring.datasource.druid.test2.driverClassName=org.postgresql.Driver

3、配置数据源注入到spring的容器中(关键点:ConfigurationProperties注解、DruidDataSourceBuilder)

@Configuration
public class DynamicDataSourceConfig {
    @Bean
    @ConfigurationProperties("spring.datasource.druid.test1")
    public DataSource timDatasource() {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return dataSource;
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.test2")
    public DataSource xresDatasource() {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return dataSource;
    }

    @Bean
    @Primary
    public DynamicDataSource dataSource(DataSource timDatasource,DataSource xresDatasource){
        Map<Object,Object> targetDataSource = new HashMap<>();
        targetDataSource.put("test1",timDatasource);
        targetDataSource.put("test2",xresDatasource);
        return new DynamicDataSource(timDatasource,targetDataSource);
    }
}

4、配置数据源管理类,用于在创建session前,切换数据源。

public class DynamicDataSource extends AbstractRoutingDataSource{
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
    private static Map<Object,Object> allTargetDataSource = null;
    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object,Object> targetDataSource){
        this.allTargetDataSource = targetDataSource;
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSource);
        super.afterPropertiesSet();
    }
    @Override
    protected Object determineCurrentLookupKey() {
        return getDataSource();
    }
    public static void setDataSource(String dataSource){
        contextHolder.set(dataSource);
    }
    public static String getDataSource(){
        return contextHolder.get();
    }
    public static void clearDataSource(){
        contextHolder.remove();
    }
}

5、自定义注解,用于定义哪些方法需要切换到哪种数据源。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SelfDataSource {
    String name() default "";
}

6、AOP切面类,用于监测使用了第五步自定义注解的方法,并调用第四步创建的数据源管理类中的方法,来切换方法执行前使用的数据源。

@Aspect
@Component
public class DataSourceAspect {
    @Pointcut("@annotation(com.wyt01datasource.modules.dynamicdatasource.SelfDataSource)")
    public void dataSourcePointCut(){
    }
    @Pointcut(value = "execution(* com.wyt01datasource.modules.service.*(..))")
    public void controlMethod(){
    }
    @Before("controlMethod()")
    public void dataSourceChange(){
        System.out.print("更改数据源为test1");
        DynamicDataSource.setDataSource("test1");
    }

    @After("controlMethod()")
    public void clearDataSource(){
        System.out.print("清除数据源test1");
        DynamicDataSource.clearDataSource();
    }

    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable{
        MethodSignature signature = (MethodSignature)point.getSignature();
        Method method = signature.getMethod();
        SelfDataSource dataSource = method.getAnnotation(SelfDataSource.class);
        if (dataSource==null){
            DynamicDataSource.setDataSource("test2");
            System.out.println("切换数据源为:test2");
        }else {
            DynamicDataSource.setDataSource(dataSource.name());
            System.out.println("切换数据源为:"+dataSource.name());
        }
        try {
            return point.proceed();
        } finally {
            DynamicDataSource.clearDataSource();
        }
    }
}

7、使用

当我们需要切换数据源时,在对应方法上添加 @SelfDataSource(name = "test1") 的注解即可

8、流程讲解

项目启动时,首先是通过ConfigurationProperties注解将属性文件中的多个数据源配置生成DataSource对象,并配置到抽象类AbstractRoutingDataSource中,该类可以通过key选择当前使用的数据源。我们在刚才使用时,就是通过静态代理Aspectj框架拦截需要切换数据源的方法,然后通过注解中需要切换的数据源的值来设置AbstractRoutingDataSource中的方法需要切换的数据源。

9、踩坑

项目启动时会自动加载数据源,而由于使用了spring-boot-starter-data-jpa依赖,在调用数据源的时候会出现循环依赖报错。这是由于springboot的自动加载机制导致的(具体原因本想追踪源码解释一下,奈何功力太浅,只追踪到了循环的方法。原理则解释不清)。其解决方式是在启动类上添加

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) 

即配置不让springboot启动时自动加载数据源。而且经过本人实现,如果不通过spring-data-jpa的方式,使用mybatis则不会出现循环依赖的问题。

如果有错误和不足之处,欢迎各位大佬指导!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值