SpringBoot中如何通过AOP实现多数据源动态切换?

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

前言

一、Springboot中实现多数据源的方法?

1. 静态数据源切换

2. 动态数据源切换(基于AOP)

二、使用AOP实现多数据源动态切换

1.引入依赖

2.在application.yml中配置多数据源

3. 数据源属性配置类

4. 数据源常量类

5. 自定义多数据源配置类

6. 利用ThreadLocal解决线程安全问题

7. 继承AbstractRoutingDataSource定义数据源标识

8. 定义多数据源注解DataSource

9. 定义DataSource注解切片

10. 在调用方法上增加注解

总结


前言

在当今的软件开发领域,许多应用需要访问多个数据源,以下是多数据源的一些重要使用场景:

1. 读写分离:在高流量应用中,为了提高性能和负载均衡,通常会采用读写分离策略。读操作和写操作被路由到不同的数据库,以减轻数据库服务器的压力。多数据源是实现这一策略的关键,它能够选择适当的数据源,以实现读写分离。

2. 数据迁移和同步:在数据迁移或数据同步操作中,需要同时连接到源数据库和目标数据库。多数据源可确保数据从一个源复制到另一个目标,或者实现不同数据库之间的数据同步。

3. 业务和技术隔离:有时一个应用程序需要同时访问多个数据库,每个数据库可能用于不同的业务或技术目的。多数据源可确保请求根据业务或技术要求路由到正确的数据源。

综上所述,多数据源在各种关键应用场景中发挥着重要作用,有助于确保数据的隔离、性能和安全性。在接下来的部分,我们将探讨Spring Boot中来实现多数据源,以满足这些复杂需求的方法。


一、Springboot中实现多数据源的方法

多数据源设置可以分为静态数据源切换和动态数据源切换两种方式:

1. 静态数据源切换

通常情况下,我们会为每个数据源配置独立的sessionFactory和DAO层代码(以Hibernate或MyBatis为例)。这种方式被称为静态数据源配置。

2. 动态数据源切换(基于AOP)

在动态数据源切换中,我们引入了面向切面编程(AOP)的概念。通过AOP,我们可以在运行时动态切换数据源,而不需要在代码中硬编码多个SessionFactory。这种基于AOP的动态数据源切换方式更加灵活、可维护,使系统更具扩展性。

二、使用AOP实现多数据源动态切换

1.引入依赖

我们使用druid数据库连接池,在pom.xml文件中引入druid的坐标:

<dependency>    
    <groupId>com.alibaba</groupId>    
    <artifactId>druid-spring-boot-starter</artifactId>    
    <version>1.2.11</version>
</dependency>

2.在application.yml中配置多数据源

配置如下:

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      master: #master数据源        
        url: jdbc:mysql://localhost:3306/hrs?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
        username: root
        password: 123456
      slave:  #slave数据源        
        url: jdbc:mysql://localhost:3306/hrs_slave?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
        username: root
        password: 123456
      # 初始连接数      
      initialSize: 5
      # 最小连接池数量      
      minIdle: 10
      # 最大连接池数量      
      maxActive: 20
      # 配置获取连接等待超时的时间      
      maxWait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒              
      timeBetweenEvictionRunsMillis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒      
      minEvictableIdleTimeMillis: 300000
      # 配置一个连接在池中最大生存的时间,单位是毫秒      
      maxEvictableIdleTimeMillis: 900000
      # 配置检测连接是否有效      
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      statViewServlet:
        enabled: true
        url-pattern: /monitor/druid

3. 数据源属性配置类

@Configuration
public class DruidProperties {
    @Value("${spring.datasource.druid.initialSize}")
    private int initialSize;

    @Value("${spring.datasource.druid.minIdle}")
    private int minIdle;

    @Value("${spring.datasource.druid.maxActive}")
    private int maxActive;

    @Value("${spring.datasource.druid.maxWait}")
    private int maxWait;

    @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
    private int timeBetweenEvictionRunsMillis;

    @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")
    private int minEvictableIdleTimeMillis;

    @Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}")
    private int maxEvictableIdleTimeMillis;

    @Value("${spring.datasource.druid.validationQuery}")
    private String validationQuery;

    @Value("${spring.datasource.druid.testWhileIdle}")
    private boolean testWhileIdle;

    @Value("${spring.datasource.druid.testOnBorrow}")
    private boolean testOnBorrow;

    @Value("${spring.datasource.druid.testOnReturn}")
    private boolean testOnReturn;

    public DruidDataSource dataSource(DruidDataSource datasource) {
        /** 配置初始化大小、最小、最大 */        
        datasource.setInitialSize(initialSize);
        datasource.setMaxActive(maxActive);
        datasource.setMinIdle(minIdle);

        /** 配置获取连接等待超时的时间 */        
        datasource.setMaxWait(maxWait);

        /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */        
        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);

        /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */        
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);

        /**         
        * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。         
        */        
        datasource.setValidationQuery(validationQuery);
        /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */        
        datasource.setTestWhileIdle(testWhileIdle);
        /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */        
        datasource.setTestOnBorrow(testOnBorrow);
        /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */        
        datasource.setTestOnReturn(testOnReturn);
        return datasource;
    }
}

4. 数据源常量类

public interface DataSourceName {
    String DEFAULT_DATASOURCE_NAME="master";
    String OTHER_DATASOURCE_NAME="slave";
}

5. 自定义多数据源配置类

@Configuration
public class DruidConfig
{
    @Bean    
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource(DruidProperties druidProperties)
    {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return druidProperties.dataSource(dataSource);
    }

    @Bean    
    @ConfigurationProperties("spring.datasource.druid.slave")
    {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return druidProperties.dataSource(dataSource);
    }

    @Bean(name = "dynamicDataSource")
    @Primary    
    public DynamicDataSource dataSource(DataSource masterDataSource, DataSource slaveDataSource)
    {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceName.DEFAULT_DATASOURCE_NAME, masterDataSource);
        targetDataSources.put(DataSourceName.OTHER_DATASOURCE_NAME, slaveDataSource);
        return new DynamicDataSource(masterDataSource, targetDataSources);
    }
}

6. 利用ThreadLocal解决线程安全问题

public class DynamicDataSourceContextHolder {
    private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);

    /**     
    * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,     
    * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。     
    */    
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    /**     
    * 设置数据源的变量     
    */    
    public static void setDateSoureName(String dsName) {
        LOGGER.info("切换到{}数据源", dsName);
        CONTEXT_HOLDER.set(dsName);
    }

    /**     
    * 获得数据源的变量     
    */    
    public static String getDateSoureName() {
        return CONTEXT_HOLDER.get();
    }

    /**     
    * 清空数据源变量     
    */    
    public static void removeDataSourceName() {
        CONTEXT_HOLDER.remove();
    }
}

7. 继承AbstractRoutingDataSource定义数据源标识

public class DynamicDataSource extends AbstractRoutingDataSource {
    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    @Override    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDateSoureName();
    }
}

8. 定义多数据源注解DataSource

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})//作用在类和方法上
// @Inherited//当以后我们在定义一个作用于类的注解时候,如果希望该注解也作用于其子类,那么可以用@Inherited 来进行修饰。
public @interface DataSource {
    String value() default DataSourceName.DEFAULT_DATASOURCE_NAME;
}

9. 定义DataSource注解切片

在transaction interpter执行之前就把动态数据源配置好,所以在动态数据源的配置的AOP切片上加入Order(1),让其先执行即可。

@Aspect
@Order(1)
@Component
public class DataSourceAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(DataSourceAspect.class);

    //定义切点,拦截@DataSource标注的类和方法    
    @Pointcut("@annotation(com.example.demo.annotation.DataSource)")
    public void pointCut(){}

    @Around("pointCut()")
    public Object useDataSource(ProceedingJoinPoint pjp) throws Throwable{
        // 获取注解中数据源名称        
        // 1.获取当前方法        
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        // 2.获取当前方法上@DataSource的值,若方法上不存在,查找类上的值        
        Method method = methodSignature.getMethod();
        DataSource dataSource = method.getAnnotation(DataSource.class);
        // 3.若注解不为空,获取值,并存入本地线程        
        if(dataSource!=null){
            String value = dataSource.value();
            //将值存入ThreadLocal中            
            LOGGER.info("切换数据源为{}", dataSource.value());
            DynamicDataSourceContextHolder.setDateSoureName(value);
        }
        //4.执行原方法,有返回值直接返回        
        try {
            return pjp.proceed();
        } finally {
            if(dataSource!=null) {
                // 销毁数据源 在执行方法之后                
                LOGGER.info("销毁数据源{}", dataSource.value());
                //5.移除本地线程的值                
                DynamicDataSourceContextHolder.removeDataSourceName();
            }
        }
    }

}

10. 在调用方法上增加注解

我们需要在方法上添加@DataSource("数据源名称")注解,这样就可以利用AOP实现动态切换了。

可以在service层方法上增加注解:

@Service
public class TbDeptServiceImpl implements TbDeptService {
    。。。
    @DataSource(value = DataSourceName.OTHER_DATASOURCE_NAME)
    @Override
    public PageVO<TbDept> pageListSlave(TbDeptQuery query) {
    
        List<TbDept> pageList = tbDeptMapper.pageList(query);
        int totalCount = tbDeptMapper.pageListCount(query);
    
        // result    
        PageVO<TbDept> result = new PageVO<>(pageList, query.getPage(), query.getSize(), totalCount);
        return result;
    }
    。。。
} 

也可以在mapper层方法上增加注解:

@Mapper
@Repository
public interface TbDeptMapper {
    。。。
    @DataSource(value = DataSourceName.OTHER_DATASOURCE_NAME)
    TbDept load(int id);
    。。。
}


总结

本文介绍了SpringBoot中,通过AOP实现多数据源动态切换的方法。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Spring Boot是一个用于简化Spring应用开发的框架,可以方便地实现多数据源动态切换功能。 在Spring Boot,通过配置多个数据源bean,可以实现多数据源的使用。首先,在application.properties或application.yml文件配置多个数据源的相关属性,例如数据库连接信息、驱动程序等。然后,在Spring Boot的配置类,通过@Bean注解将数据源bean配置到Spring容器。 为了实现动态切换数据源,可以使用ThreadLocal或者AOP进行数据源切换。首先,可以定义一个DataSourceContextHolder类,使用ThreadLocal来存储当前线程使用的数据源标识。然后,可以在需要切换数据源的地方,通过调用DataSourceContextHolder的setDataSourceKey方法,设置当前线程使用的数据源标识。在数据访问层的代码,可以通过调用DataSourceContextHolder的getDataSourceKey方法获取当前线程使用的数据源标识,然后根据该标识来获取对应的数据源bean。 除了使用ThreadLocal,还可以使用AOP实现数据源切换。可以定义一个切面,通过在切面获取注解或者方法名来确定使用的数据源,然后通过切换数据源的方式来实现动态切换。 通过以上的方式,就可以实现Spring Boot多数据源动态切换功能。不同的数据源可以根据自己的需求和业务场景进行配置和使用,提高系统的扩展性和灵活性。 ### 回答2: Spring Boot提供了方便的配置和集成多数据源,并且支持动态切换数据源。下面将通过一个简单的示例来说明如何在Spring Boot实现多数据源动态切换。 首先,在pom.xml文件添加Spring Boot数据源相关的依赖项,比如Spring Boot Starter、MyBatis、Druid等。然后,在application.properties文件配置数据源的相关信息,包括数据库的URL、用户名、密码等。 接下来,创建多个数据源的配置类,通过@Configuration注解将其标记为配置类,并使用@Bean注解来创建数据源对象。在创建数据源对象时,可以使用DruidDataSource或者其他适配的数据源类。 在创建数据源配置类时,可以使用@Primary注解来指定一个主数据源,该主数据源将作为默认数据源使用。对于其他数据源,可以使用@Qualifier注解进行标识。 然后,在创建MyBatis的SqlSessionFactoryBean时,使用@MapperScan注解来扫描并加载Mapper接口。在其配置数据源,可以通过注入的方式获取数据源对象,并将其设置到SqlSessionFactoryBean。 最后,在需要切换数据源的地方,可以通过使用AOP切面和动态切换数据源的方式来实现。可以创建一个DataSourceAspect切面类,在其定义切点和通知,通过在方法上添加@DataSource注解来指定要切换数据源。在通知方法,通过读取注解上的参数来确定要切换数据源,并将其设置到ThreadLocal变量。 总结起来,Spring Boot多数据源动态切换的步骤包括添加依赖项、配置数据源、创建数据源配置类、配置MyBatis的SqlSessionFactoryBean以及使用AOP实现动态切换数据源。通过这些步骤,我们可以在Spring Boot轻松实现多数据源动态切换。 ### 回答3: 在Spring Boot实现多数据源动态切换可以通过以下步骤实现: 1. 配置数据源:在application.properties或yml文件配置多个数据源的连接信息,每个数据源都有独立的数据源配置属性。 2. 创建数据源工厂:使用Spring的Bean注解创建多个数据源对象,并分别设置其相关属性。 3. 创建数据源路由器:创建一个数据源路由器类,该类包含一个ThreadLocal变量,用于保存当前线程所使用的数据源标识。 4. 创建数据源切换注解:使用自定义注解方式,通过在Service层的方法上加上注解,来选择对应的数据源。 5. 创建切面:使用AOP的方式,在方法执行前获取选择的数据源标识,并存入数据源路由器的ThreadLocal变量。 6. 创建数据源切换切入点:在切面设置一个切入点,用于匹配加上数据源切换注解的方法。 7. 配置数据源切面:使用Spring的Bean注解配置切面类。 8. 启动类配置数据源切换:在启动类添加@EnableAspectJAutoProxy注解来开启AOP,同时使用@Import注解引入切面类。 9. 使用数据源切换注解:在Service层的方法上加上数据源切换注解,指定使用哪个数据源。 通过以上步骤,就可以在使用Spring Boot实现多数据源动态切换。在需要切换数据源的地方,只需要使用自定义的注解来指定数据源切换的过程由切面和数据源路由器来完成。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值