SpringBoot 的多数据源配置与动态切换

Posted 潇潇雨歇_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot 的多数据源配置与动态切换相关的知识,希望对你有一定的参考价值。

在使用SpringBoot开发项目时,随着业务量的扩大,我们通常会进行数据库拆分或引入其他数据库,因此需要配置多个数据源,并能实现其动态切换。

一.工程目录截图

二.相关代码

 2.1 多数据源application.yml配置文件

# 数据源配置
spring:
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.cj.jdbc.Driver
        druid:
            # 主库数据源
            master:
                url: jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                username: root
                password: 123456
            # 从库数据源popedom
            slave:
                # 从数据源开关/默认关闭
                enabled: true
                url: jdbc:mysql://localhost:3306/test_slave_db?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                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
            webStatFilter:
                enabled: true
            statViewServlet:
                enabled: true
                # 设置白名单,不填则允许所有访问
                allow:
                url-pattern: /druid/*
                # 控制台管理用户名和密码
                login-username: vrmp
                login-password: 123456
            filter:
                stat:
                    enabled: true
                    # 慢SQL记录
                    log-slow-sql: true
                    slow-sql-millis: 1000
                    merge-sql: true
                wall:
                    config:
                        multi-statement-allow: true

2.2 自定义注解DataSource

package com.aliyun.datasource.common.annotation;

import com.aliyun.datasource.common.enums.DataSourceType;

import java.lang.annotation.*;

/**
 * 自定义多数据源切换注解
 * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
 * @author haige
 */
@Target( ElementType.METHOD, ElementType.TYPE )
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource

    /**
     * 切换数据源名称
     */
    public DataSourceType value() default DataSourceType.MASTER;

2.3 主从库枚举定义

package com.aliyun.datasource.common.enums;

/**
 * 数据源
 * 
 * @author haige
 */
public enum DataSourceType

    /**
     * 主库
     */
    MASTER("master"),

    /**
     * 从库
     */
    SLAVE("slave");

    private String value;

    DataSourceType(String value) 
        this.value = value;
    

    public String getValue() 
        return value;
    

2.4 数据源的切换处理

package com.aliyun.datasource.common.holder;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 数据源切换处理
 * 
 * @author haige
 */
public class DynamicDataSourceContextHolder

    public static final Logger log = LoggerFactory.getLogger(com.aliyun.datasource.common.holder.DynamicDataSourceContextHolder.class);

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

    /**
     * 设置数据源的变量
     */
    public static void setDataSourceType(String dsType)
    
        log.info("切换到数据源", dsType);
        CONTEXT_HOLDER.set(dsType);
    

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

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

2.5 数据源注解的AOP拦截处理

package com.aliyun.datasource.common.aspectj;

import com.aliyun.datasource.common.annotation.DataSource;
import com.aliyun.datasource.common.holder.DynamicDataSourceContextHolder;
import com.aliyun.datasource.common.util.StringUtils;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Objects;

/**
 * 多数据源处理
 * 
 * @author haige
 */
@Aspect
@Order(1)
@Component
public class DataSourceAspect

    protected Logger logger = LoggerFactory.getLogger(getClass());

    @Pointcut("@annotation(com.aliyun.datasource.common.annotation.DataSource)"
            + "|| @within(com.aliyun.datasource.common.annotation.DataSource)")
    public void dsPointCut()
    

    

    @Around("dsPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable
    
        DataSource dataSource = getDataSource(point);

        if (StringUtils.isNotNull(dataSource))
        
            DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
        

        try
        
            return point.proceed();
        
        finally
        
            // 销毁数据源 在执行方法之后
            DynamicDataSourceContextHolder.clearDataSourceType();
        
    

    /**
     * 获取需要切换的数据源
     */
    public DataSource getDataSource(ProceedingJoinPoint point)
    
        MethodSignature signature = (MethodSignature) point.getSignature();
        DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
        if (Objects.nonNull(dataSource))
        
            return dataSource;
        

        return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
    

2.6 数据源bean的配置注入

package com.aliyun.datasource.common.configuration;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.aliyun.datasource.common.enums.DataSourceType;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
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 org.springframework.context.annotation.Profile;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
@MapperScan("com.aliyun.datasource.test.mapper*")
public class MyBatisPlusConfiguration 

    /*
     * 分页插件,自动识别数据库类型
     * 多租户,请参考官网【插件扩展】
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() 
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        // 开启 PageHelper 的支持
//         paginationInterceptor.setL(true);
        return paginationInterceptor;
    

    /**
     * SQL执行效率插件
     */
    @Bean
    @Profile("local","dev")// 设置 dev test 环境开启
    public PerformanceInterceptor performanceInterceptor() 
        PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
        performanceInterceptor.setMaxTime(1000);
        performanceInterceptor.setFormat(true);
        return performanceInterceptor;
    

    @Bean(name = "master")
    @ConfigurationProperties(prefix = "spring.datasource.druid.master" )
    public DataSource master() 
        return DruidDataSourceBuilder.create().build();
    

    @Bean(name = "slave")
    @ConfigurationProperties(prefix = "spring.datasource.druid.slave" )
    public DataSource slave() 
        return DruidDataSourceBuilder.create().build();
    

    /**
     * 动态数据源配置
     * @return
     */
    @Bean
    @Primary
    public DataSource multipleDataSource(@Qualifier("master") DataSource master, @Qualifier("slave") DataSource slave) 
        MultipleDataSource multipleDataSource = new MultipleDataSource();
        Map< Object, Object > targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.MASTER.getValue(), master);
        targetDataSources.put(DataSourceType.SLAVE.getValue(), slave);
        //添加数据源
        multipleDataSource.setTargetDataSources(targetDataSources);
        //设置默认数据源
        multipleDataSource.setDefaultTargetDataSource(master);
        return multipleDataSource;
    

    @Bean("sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory() throws Exception 
        MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(multipleDataSource(master(),slave()));
        //sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/*/*Mapper.xml"));

        MybatisConfiguration configuration = new MybatisConfiguration();
        //configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
        configuration.setJdbcTypeForNull(JdbcType.NULL);
        configuration.setMapUnderscoreToCamelCase(true);
        configuration.setCacheEnabled(false);
        sqlSessionFactory.setConfiguration(configuration);
        sqlSessionFactory.setPlugins(new Interceptor[]
                paginationInterceptor() //添加分页功能
        );
        //sqlSessionFactory.setGlobalConfig(globalConfiguration());
        return sqlSessionFactory.getObject();
    

2.7 数据源切换测试

package com.aliyun.datasource.test.controller;


import com.aliyun.datasource.common.util.Result;
import com.aliyun.datasource.common.util.StatusCode;
import com.aliyun.datasource.test.entity.VideoRoom;
import com.aliyun.datasource.test.service.IVideoRoomService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

/**
* <p>
* 前端控制器
* @author haige
* @since 2021-11-24
*/

@Slf4j
@RestController
@RequestMapping("/videoRoom")
public class VideoRoomController 

    @Autowired
    public IVideoRoomService videoRoomService;

    /**
     * 测试多数据源动态切换
     */
    @GetMapping("/testDatasources")
    public Result testDataSource()
        List<VideoRoom> roomList = new ArrayList<>();
        roomList.add(videoRoomService.testMasterData());
        roomList.add(videoRoomService.testSlaveData());
        roomList.add(videoRoomService.testMasterData());
        return new Result(StatusCode.SUCCESS,"查询成功",roomList);
    



@Service
public class VideoRoomServiceImpl extends ServiceImpl<VideoRoomMapper, VideoRoom> implements IVideoRoomService 

    @Override
//    @DataSource(DataSourceType.MASTER)   不用注解则默认使用MASTER数据源
    public VideoRoom testMasterData() 
        return this.baseMapper.selectById(1);
    

    @Override
    @DataSource(DataSourceType.SLAVE)
    public VideoRoom testSlaveData() 
        return this.baseMapper.selectById(5);
    

三.测试结果

 

 四.工程完整代码

SpringBoot多数据源切换.zip

以上是关于SpringBoot 的多数据源配置与动态切换的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot 的多数据源配置与动态切换

Spring动态配置多数据源的基于spring和ibatis的多数据源切换方案

SpringBoot与动态多数据源切换

springboot基于方法级别注解事务的多数据源切换问题

基于Spring+Mybatis的多数据源动态切换

springboot+多数据源配置