加入收藏 | 设为首页 | 会员中心 | 我要投稿 北几岛 (https://www.beijidao.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

SpringBoot实现多数据源动态切换

发布时间:2021-07-06 05:27:45 所属栏目:大数据 来源: https://blog.csdn.net/j123123
导读:一.多数据源切换 1.在配置文件中,配置3个不同的数据源,如下(项目使用的是druid数据库连接池): spring: datasource: druid: # 数据库连接1 datasource1: driver-class-name: com.MysqL.cj.jdbc.Driver url: jdbc:MysqL://127.0.0.1/datasource_1?autoRec

一.多数据源切换

1.在配置文件中,配置3个不同的数据源,如下(项目使用的是druid数据库连接池):

spring:
  datasource:
    druid:
      # 数据库连接1
      datasource1:
        driver-class-name: com.MysqL.cj.jdbc.Driver
        url: jdbc:MysqL://127.0.0.1/datasource_1?autoReconnect=true&roundRobinLoadBalance=true&useUnicode=yes&characterEncoding=utf8&connectionCollation=utf8_general_ci&serverTimezone=GMT%2B8
        username: root
        password: 
        initialSize: 5
        minIdle: 5
        maxActive: 20
        # 配置获取连接等待超时的时间
        maxWait: 60000
        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        timeBetweenEvictionRunsMillis: 60000
        # 配置一个连接在池中最小生存的时间,单位是毫秒
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        # 打开PSCache,并且指定每个连接上PSCache的大小
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
        filters: stat,wall
        # 通过connectProperties属性来打开mergesql功能;慢sql记录
        connectionProperties: druid.stat.mergesql=true;druid.stat.slowsqlMillis=5000
      
      # 数据库连接2
      datasource2:
        driver-class-name: com.MysqL.cj.jdbc.Driver
        url: jdbc:MysqL://127.0.0.1/datasource_2?autoReconnect=true&roundRobinLoadBalance=true&useUnicode=yes&characterEncoding=utf8&connectionCollation=utf8_general_ci&serverTimezone=GMT%2B8
        username: root
        password: 
        initialSize: 5
        minIdle: 5
        maxActive: 20
        # 配置获取连接等待超时的时间
        maxWait: 60000
        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        timeBetweenEvictionRunsMillis: 60000
        # 配置一个连接在池中最小生存的时间,单位是毫秒
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        # 打开PSCache,并且指定每个连接上PSCache的大小
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
        filters: stat,wall
        # 通过connectProperties属性来打开mergesql功能;慢sql记录
        connectionProperties: druid.stat.mergesql=true;druid.stat.slowsqlMillis=5000
      
      # # 数据库连接3
      datasource3:
        driver-class-name: com.MysqL.cj.jdbc.Driver
        url: jdbc:MysqL://127.0.0.1/datasource_3?useUnicode=yes&characterEncoding=utf8&connectionCollation=utf8_general_ci&serverTimezone=GMT%2B8
        username: root
        password: 
        initialSize: 5
        minIdle: 5
        maxActive: 20
        # 配置获取连接等待超时的时间
        maxWait: 60000
        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        timeBetweenEvictionRunsMillis: 60000
        # 配置一个连接在池中最小生存的时间,单位是毫秒
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        # 打开PSCache,并且指定每个连接上PSCache的大小
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
        filters: stat,wall
        # 通过connectProperties属性来打开mergesql功能;慢sql记录
        connectionProperties: druid.stat.mergesql=true;druid.stat.slowsqlMillis=5000

2.定义类DynamicDataSource继承AbstractRoutingDataSource实现determineCurrentLookupKey方法,该方法可以实现数据库的动态切换,如下:

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}

3.定义一个可以设置当前线程的变量的工具类,用于设置对应的数据源名称:

/**
 * @Decription: 动态数据源配置
 **/

@Slf4j
public class DataSourceContextHolder {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

    /** 
     * @Description: 设置数据源类型 
     * @param dbType 数据源名称
     * @return void
     */  
    public static void setDataSource(String dbType){
        log.info("====>切换到 ["+dbType+"] 数据源");
        contextHolder.set(dbType);
    }
    
    /** 
     * @Description: 获取数据源类型
     * @return String
     */
    public static String getDataSource(){
        return contextHolder.get();
    }

    /**
     * @Description: 清除本地线程使用的数据源,使用默认的数据源
     * @return void
     **/
    public static void clearDataSource(){
        contextHolder.remove();
    }
}

4.新建配置类,配置多数据源和注解事务

/**
 * @Decription: 动态多数据源配置
 **/
@Configuration
public class MutiplyDataSource {
    /**
     * 数据源1
     * @return
     */
    @Bean(name = "dataSource1")
    @ConfigurationProperties(prefix = "spring.datasource.druid.datasource1")
    public DataSource dataSource1(){
        return new DruidDataSource();
    }

    /**
     * 数据源2
     * @return
     */
    @Bean(name = "dataSource2")
    @ConfigurationProperties(prefix = "spring.datasource.druid.datasource2")
    public DataSource dataSource2(){
        return new DruidDataSource();
    }
    
    /**
     * 数据源3
     * @return
     */
    @Bean(name = "dataSource3")
    @ConfigurationProperties(prefix = "spring.datasource.druid.datasource3")
    public DataSource dataSource3(){
        return new DruidDataSource();
    }

    @Primary
    @Bean(name = "dynamicDataSource")
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        //配置默认数据源
        dynamicDataSource.setDefaultTargetDataSource(dataSource1());

        //配置多数据源
        HashMap<Object,Object> dataSourceMap = new HashMap();
        dataSourceMap.put(ContextConst.DataSourceType.DATASOURCE_1.name(),dataSource1());
        dataSourceMap.put(ContextConst.DataSourceType.DATASOURCE_2.name(),dataSource2());
        dataSourceMap.put(ContextConst.DataSourceType.DATASOURCE_3.name(),dataSource3());
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        return dynamicDataSource;
    }

    /**
     * 配置@Transactional注解事务
     * @return
     */
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }
}

记得在入口函数加事务配置注解@EnableTransactionManagement以及排除自动注入数据源的配置:

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableTransactionManagement
@EnableAsync
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class,args);
    }

}

5.定义一个aop处理类在数据库事务开启之前切换数据源,如下:

/**
 * @Decription: 动态数据源配置 @Order(-10) :在事务开启前切换数据源
 **/
@Component
@Aspect
@Order(-10)
@Slf4j
public class DynamicDataSourceAspect {
    @Autowired
    private EntityManager entityManager;

    /**
     * 服务类中的方法开始前,如果有@DataSource,会断开之前的连接,关闭之前的事务(一个事务对应一个数据源)
     **/
    @Before("execution(* com.jcy.demo.service.*.*(..)) && @annotation(com.jcy.demo.annotation.DataSource)")
    public void before(JoinPoint point){
        try {
            DataSource annotationOfClass = point.getTarget().getClass().getAnnotation(DataSource.class);
            String methodName = point.getSignature().getName();
            Class[] parameterTypes = ((MethodSignature) point.getSignature()).getParameterTypes();
            Method method = point.getTarget().getClass().getMethod(methodName,parameterTypes);
            DataSource methodAnnotation = method.getAnnotation(DataSource.class);
            methodAnnotation = methodAnnotation == null ? annotationOfClass:methodAnnotation;
            ContextConst.DataSourceType dataSourceType = methodAnnotation != null && methodAnnotation.value() !=null ? methodAnnotation.value() :ContextConst.DataSourceType.DATASOURCE_1;
            DataSourceContextHolder.setDataSource(dataSourceType.name());
            SessionImplementor session = entityManager.unwrap(SessionImplementor.class);
            //最关键的一句代码, 手动断开连接,不用重新设置 ,会自动重新设置连接
            session.disconnect();
        } catch (NoSuchMethodException e) {
            log.error("切换动态数据源发生异常",e);
        } catch(Exception e){
        	log.error("切换动态数据源发生异常",e);
        }
    }

    /**
     * 服务类的方法结束后,会清除数据源,此时会变更为默认的数据源
     **/
    @After("execution(* com.jcy.demo.service.*.*(..)) && @annotation(com.jcy.demo.annotation.DataSource)")
    public void after(JoinPoint point){
        DataSourceContextHolder.clearDataSource();
    }
}

利用aop的order属性设置执行的顺序,这样在事务开启前就可以切换数据源了。

二.业务代码测试实现

1.第二个数据源的service示例如下:

@Service
@Slf4j
public class FoodService {
    @Autowired
    private FoodRepository foodRepository;
    
    @DataSource(ContextConst.DataSourceType.DATASOURCE_2)
    @Transactional(rollbackFor = Exception.class)
    public Food getFoodByName(String name){
    	log.info("访问第二个数据源,获得零食信息",name);
        return foodRepository.findByFoodName(name);
    }
}

2.在controller中写一个接口实现3个数据源的切换:

@Api(tags = "多数据源demo")
@RestController
@Slf4j
public class DemoController {
	
    @Autowired
    private UserService userService;
	
    @Autowired
    private AddressService addressService;
	
    @Autowired
    private FoodService foodService;

    @ApiOperation("多数据源切换")
    @RequestMapping(value = "/changeDatasource")
    public Ret index() {
        Ret result = Ret.create();
        // 访问默认数据源
        User user = userService.findByAccount("喵");
        // 访问第二个数据源
        Food food = foodService.getFoodByName("鸡腿");
        // 访问第三个数据源
        Address address = addressService.saveAddress(1L);
        log.info("用户为:{},爱吃的零食为:{},更改后的地址为:{}",user.getUsername(),food.getFoodName(),address.getAddress());
        result.setCodeAndMsg(200);
        return result;
    }
}

代码已上传到git,路径为:

多数据源动态切换demo代码

(编辑:北几岛)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读