Spring boot 使用AbstractRoutingDataSource实现数据源动态切换

2024-06-15 1232阅读

目录

一、AbstractRoutingDataSource

二、具体实现

1、pom.xml

2、新建UserMapper

3、在spring boot 启动类上添加扫描mapper注解

4、在配置文件 application.properties 中添加多个(我这里是两个)数据源的配置信息

5、集成动态数据源模块

5.1、新建注解 CurDataSource 指定要使用的数据源

5.2、新建常量存储于获取数据源

5.3、新建类 DynamicDataSource

5.4、新建多数据源配置类

5.5、采用aop的方式

5.6、启动类上添加数据源配置

5.7、测试数据源切换


一、AbstractRoutingDataSource

Spring boot提供了AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源,这样我们可以在执行查询之前,设置使用的数据源。实现可动态路由的数据源,在每次数据库查询操作前执行。它的抽象方法 determineCurrentLookupKey() 决定使用哪个数据源。

org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource 源码的介绍:

Spring boot 使用AbstractRoutingDataSource实现数据源动态切换

大概意思是:

AbstractRoutingDataSource的getConnection() 方法根据查找 lookup key 键对不同目标数据源的调用,通常是通过(但不一定)某些线程绑定的事物上下文来实现。

AbstractRoutingDataSource的多数据源动态切换的核心逻辑是:在程序运行时,把数据源数据源通过 AbstractRoutingDataSource 动态织入到程序中,灵活的进行数据源切换。

基于AbstractRoutingDataSource的多数据源动态切换,可以实现读写分离,这么做缺点也很明显,无法动态的增加数据源。

 

实现逻辑:

定义DynamicDataSource类继承抽象类AbstractRoutingDataSource,并实现了determineCurrentLookupKey()方法。

把配置的多个数据源会放在AbstractRoutingDataSource的 targetDataSources和defaultTargetDataSource中,然后通过afterPropertiesSet()方法将数据源分别进行复制到resolvedDataSources和resolvedDefaultDataSource中。

调用AbstractRoutingDataSource的getConnection()的方法的时候,先调用determineTargetDataSource()方法返回DataSource在进行getConnection()。

详细解析跳转此链接查看利用AbstractRoutingDataSource实现动态数据源切换determineCurrentLookupKey方法-CSDN博客

二、具体实现

代码结构

Spring boot 使用AbstractRoutingDataSource实现数据源动态切换

1、pom.xml

新建springboot项目,其中pom.xml 文件依赖如下

        
            org.springframework.boot
            spring-boot-starter-aop
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter
        
        
        
            com.alibaba
            druid-spring-boot-starter
            1.1.10
        
        
        
            org.apache.commons
            commons-lang3
            3.9
        
        
        
            com.mysql
            mysql-connector-j
            runtime
        
        
            org.mybatis
            mybatis
            3.5.6
        
        
        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            2.2.0
        
        
        
            io.springfox
            springfox-swagger2
            2.9.2
        
        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
            org.xmlunit
            xmlunit-core
        
    
2、新建UserMapper

在新建两个数据库,新建两个表,可以如下,也可以自己定义

实体User

@Data
public class User {
    private String name;
    private String address;
    private int number;
}

UserMapper

@Mapper
public interface UserMapper {
    @Select("select * from userdata")
    List getAllUsers();
}
3、在spring boot 启动类上添加扫描mapper注解
注意
@MapperScan("com.example.dynamicdata.mapper")

如下图,但是mapper路径要改成自己存放mapper文件的  文件夹相对路径

Spring boot 使用AbstractRoutingDataSource实现数据源动态切换

其中路径为mapper的相对路径

4、在配置文件 application.properties 中添加多个(我这里是两个)数据源的配置信息
#下面这些内容是为了让MyBatis映射
#指定Mybatis的Mapper文件
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
mybatis.mapper-locations=classpath:com.com.example.dynamicdata.mapper/*.xml
# 应用服务 WEB 访问端口
server.port=8080
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# 数据源1
spring.datasource.druid.first.url=jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&maxReconnects=10&allowMultiQueries=true
spring.datasource.druid.first.username=root
spring.datasource.druid.first.password=123456
# 数据源2  需要创建对应数据库  更改该库中 sys_user 表
spring.datasource.druid.second.url=jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&maxReconnects=10&allowMultiQueries=true
spring.datasource.druid.second.username=root
spring.datasource.druid.second.password=123456

如果还要添加数据源就按照 这种格式继续往下写。

5、集成动态数据源模块
5.1、新建注解 CurDataSource 指定要使用的数据源
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CurDataSource {
    String name() default "";
}
5.2、新建常量存储于获取数据源
public interface DataSourceNames {
    String FIRST = "first";
    String SECOND = "second";
}
5.3、新建类 DynamicDataSource

DynamicDataSource扩展Spring的AbstractRoutingDataSource抽象类,重写 determineCurrentLookupKey() 方法

public class DynamicDataSource extends AbstractRoutingDataSource {
    /**
     * ThreadLocal 用于提供线程局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程里的变量。
     * 也就是说 ThreadLocal 可以为每个线程创建一个【单独的变量副本】,相当于线程的 private static 类型变量。
     */
    private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal();
    /**
     * 决定使用哪个数据源之前需要把多个数据源的信息以及默认数据源信息配置好
     *
     * @param defaultTargetDataSource 默认数据源
     * @param targetDataSources       目标数据源
     */
    public DynamicDataSource(DataSource defaultTargetDataSource, Map targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }
    @Override
    protected Object determineCurrentLookupKey() {
        return getDataSource();
    }
    public static void setDataSource(String dataSource) {
        CONTEXT_HOLDER.set(dataSource);
    }
    public static String getDataSource() {
        return CONTEXT_HOLDER.get();
    }
    public static void clearDataSource() {
        CONTEXT_HOLDER.remove();
    }
}
5.4、新建多数据源配置类

配置多数据源的信息,生成多个(我这里是两个,对应application.properties中定义的数据源)数据源

注意

此处

@ConfigurationProperties("spring.datasource.druid.first"),second也一样

中的内容写application.properties中数据库配置的前半截,参考我这个写,不用写后边的url

package com.example.dynamicdata.configs;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.example.dynamicdata.common.DataSourceNames;
import com.example.dynamicdata.common.DynamicDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
 * 配置多数据源
 * @author btyuan
 * @version V1.0.0
 */
@Configuration
public class DynamicDataSourceConfig {
    @Bean
    @ConfigurationProperties("spring.datasource.druid.first")
    public DataSource firstDataSource(){
        return DruidDataSourceBuilder.create().build();
    }
    @Bean
    @ConfigurationProperties("spring.datasource.druid.second")
    public DataSource secondDataSource(){
        return DruidDataSourceBuilder.create().build();
    }
    @Bean
    @Primary
    public DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource) {
        Map targetDataSources = new HashMap(5);
        targetDataSources.put(DataSourceNames.FIRST, firstDataSource);
        targetDataSources.put(DataSourceNames.SECOND, secondDataSource);
        return new DynamicDataSource(firstDataSource, targetDataSources);
    }
}
5.5、采用aop的方式

在需要修改数据源的地方使用注解方式去切换,然后切面修改ThreadLocal的内容

此处要注意

@Pointcut("@annotation(com.example.dynamicdata.common.CurDataSource)")

中的路径为CurDataSource文件相对路径,这是我的,你要改成自己新建CurDataSource文件的目录

package com.example.dynamicdata.utils;
import com.example.dynamicdata.common.CurDataSource;
import com.example.dynamicdata.common.DataSourceNames;
import com.example.dynamicdata.common.DynamicDataSource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
 * 多数据源,切面处理类
 *
 * @author btyuan
 * @version V1.0.0
 */
@Slf4j
@Aspect
@Component
public class DataSourceAspect implements Ordered {
    @Pointcut("@annotation(com.example.dynamicdata.common.CurDataSource)")
    public void dataSourcePointCut() {
    }
    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        CurDataSource ds = method.getAnnotation(CurDataSource.class);
        if (ds == null) {
            DynamicDataSource.setDataSource(DataSourceNames.FIRST);
            log.debug("set datasource is " + DataSourceNames.FIRST);
        } else {
            DynamicDataSource.setDataSource(ds.name());
            log.debug("set datasource is " + ds.name());
        }
        try {
            return point.proceed();
        } finally {
            DynamicDataSource.clearDataSource();
            log.debug("clean datasource");
        }
    }
    @Override
    public int getOrder() {
        return 1;
    }
}
5.6、启动类上添加数据源配置

因为数据源是自己生成的,所以要去掉原先springboot启动时候自动装配的数据源配置。

注意

@MapperScan("com.example.dynamicdata.mapper")是mapper文件夹的相对路径
DynamicDataSourceConfig.class类名称不能写错,是上边5.4的配置类名称
package com.example.dynamicdata;
import com.example.dynamicdata.configs.DynamicDataSourceConfig;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Import;
@MapperScan("com.example.dynamicdata.mapper")
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@Import({DynamicDataSourceConfig.class})
public class DynamicDataApplication {
    public static void main(String[] args) {
        SpringApplication.run(DynamicDataApplication.class, args);
    }
}
5.7、测试数据源切换

service中定义两个查询,分别查两个数据库:

service

public interface SysUserService  {
    List findUserByFirstDb();
    List findUserBySecondDb();
}

实现类:因为默认是使用第一个数据源,所以不用注解,使用数据源二需要添加注解 @CurDataSource(name = DataSourceNames.SECOND) 。

serviceImpl

注意

注解@Service不要忘了写

@Service
public class SysUserServiceImpl implements SysUserService {
    @Autowired
    private UserMapper userMapper;
    @Override
    public List findUserByFirstDb() {
        List allUsers = userMapper.getAllUsers();
        return allUsers;
    }
    @CurDataSource(name = DataSourceNames.SECOND)
    @Override
    public List findUserBySecondDb() {
        List allUsers = userMapper.getAllUsers();
        return allUsers;
    }
}

测试类Controller

package com.example.dynamicdata.controller;
import com.example.dynamicdata.entity.User;
import com.example.dynamicdata.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/dynamic")
public class UserController {
    @Autowired
    private SysUserService sysUserService;
    @RequestMapping("/data")
    private List getUserData(){
        List userList = new ArrayList();
        List userByFirstDb = sysUserService.findUserByFirstDb();
        log.info("第一个数据库的User数据:" + userByFirstDb);
        List userBySecondDb = sysUserService.findUserBySecondDb();
        log.info("第二个数据库的User数据:" + userBySecondDb);
        userList.addAll(userByFirstDb);
        userList.addAll(userBySecondDb);
        return userList;
    }
}

访问测试

本地访问

http://localhost:8080/dynamic/data

如下

Spring boot 使用AbstractRoutingDataSource实现数据源动态切换

VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]