• SpringBoot实现多数据源的两种方式


    前言

    公司项目有连接多个不同数据库的需求,特研究了一下,根据网上的资料,造了一个基于AOP方式的数据源切换轮子,但继续探索,突然发现有开源的多数据源管理启动器。不过,本篇两种方式都会介绍。

    基于dynamic-datasource实现多数据源

    dynamic-datasource介绍

    dynamic-datasource-spring-boot-starter 是一个基于springboot的快速集成多数据源的启动器。

    其支持 Jdk 1.7+, SpringBoot 1.4.x 1.5.x 2.x.x

    dynamic-datasource特性

    • 支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。
    • 支持数据库敏感配置信息 加密 ENC()。
    • 支持每个数据库独立初始化表结构schema和数据库database。
    • 支持无数据源启动,支持懒加载数据源(需要的时候再创建连接)。
    • 支持 自定义注解 ,需继承DS(3.2.0+)。
    • 提供并简化对Druid,HikariCp,BeeCp,Dbcp2的快速集成。
    • 提供对Mybatis-Plus,Quartz,ShardingJdbc,P6sy,Jndi等组件的集成方案。
    • 提供 自定义数据源来源 方案(如全从数据库加载)。
    • 提供项目启动后 动态增加移除数据源 方案。
    • 提供Mybatis环境下的 纯读写分离 方案。
    • 提供使用 spel动态参数 解析数据源方案。内置spel,session,header,支持自定义。
    • 支持 多层数据源嵌套切换 。(ServiceA >>> ServiceB >>> ServiceC)。
    • 提供 基于seata的分布式事务方案。
    • 提供 本地多数据源事务方案。 附:不能和原生spring事务混用。

    我们目前只探讨使用dynamic-datasource进行数据源切换,其他请自行搜索

    dynamic-datasource的相关约定

    1. dynamic-datasource只做 切换数据源 这件核心的事情,并不限制你的具体操作,切换了数据源可以做任何CRUD。
    2. 配置文件所有以下划线 _ 分割的数据源 首部 即为组的名称,相同组名称的数据源会放在一个组下。
    3. 切换数据源可以是组名,也可以是具体数据源名称。组名则切换时采用负载均衡算法切换。
    4. 默认的数据源名称为 master ,你可以通过 spring.datasource.dynamic.primary 修改。
    5. 方法上的注解优先于类上注解。
    6. DS支持继承抽象类上的DS,暂不支持继承接口上的DS。

    引入dynamic-datasource依赖

    
      com.baomidou
      dynamic-datasource-spring-boot-starter
      ${version}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    配置数据源

    spring:
      datasource:
        dynamic:
          primary: mysql #设置默认的数据源或者数据源组,默认值即为master
          strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
          datasource:
            mysql:
              url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
              username: root
              password: 123456
              driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
            pgsql:
              url: ENC(xxxxx) # 内置加密
              username: ENC(xxxxx)
              password: ENC(xxxxx)
              driver-class-name: org.postgresql.Driver
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    使用 @DS 切换数据源

    @DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解

    注解

    结果

    不使用@DS注解

    默认数据源,即primary: mysql

    @DS(“dsName”)

    dsName可以为组名也可以为具体某个库的名称

    @DS使用实例

    @Service
    @DS("mysql")
    public class UserServiceImpl implements UserService {
    
      @Autowired
      private JdbcTemplate jdbcTemplate;
    
      // 不使用@DS注解则代表使用默认数据源
      // 如果类上存在,则使用类上标注的数据源
      public List selectAll() {
        return  jdbcTemplate.queryForList("select * from user");
      }
      
      @Override
      @DS("pgsql")
      // 方法上注解 优先于 类上注解,即使类上标注也优先采用方法上的标注
      public List selectByCondition() {
        return  jdbcTemplate.queryForList("select * from user where age >10");
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    基于AOP手动实现多数据源

    本次代码参考 https://github.com/mianshenglee/my-example/tree/master/multi-datasource/dynamic-datasource ,因源码不满足的需求,因此我在此基础做了修改。

    项目工程结构

    在这里插入图片描述

    项目依赖

    
    
        4.0.0
        
            org.springframework.boot
            spring-boot-starter-parent
            2.2.2.RELEASE
             
        
        me.mason.demo
        dynamic-datasource
        0.0.1-SNAPSHOT
        dynamic-datasource
        Demo project for dynamic datasource
    
        
            1.8
        
    
        
            
            
                org.springframework.boot
                spring-boot-starter-web
            
            
                org.springframework.boot
                spring-boot-configuration-processor
            
            
                com.alibaba
                druid-spring-boot-starter
                1.1.9
            
            
                org.springframework.boot
                spring-boot-starter-jdbc
            
            
                org.springframework.boot
                spring-boot-starter-aop
            
            
            
                mysql
                mysql-connector-java
                runtime
            
            
            
                com.baomidou
                mybatis-plus-boot-starter
                3.3.0
            
            
                org.projectlombok
                lombok
                true
            
            
                org.springframework.boot
                spring-boot-starter-test
                test
                
                    
                        org.junit.vintage
                        junit-vintage-engine
                    
                
            
    
        
    
        
            
                
                    org.springframework.boot
                    spring-boot-maven-plugin
                
            
        
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84

    配置文件

    server.port=8080
    server.servlet.context-path=/dd
    
    logging.level.root=INFO
    logging.level.me.mason.demo.dynamicdatasource.mapper=DEBUG
    
    # mybatis-plus
    mybatis-plus.type-aliases-package=me.mason.demo.dynamicdatasource.entity
    # 默认位置,可不配置
    #mybatis-plus.mapper-locations=classpath*:/mapper/*.xml
    mybatis.mapper-locations=classpath*:/mapper/*.xml
    # 使用数据库自增ID
    mybatis-plus.global-config.db-config.id-type=auto
    
    # master
    spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.master.url=jdbc:mysql://10.0.1.243:3306/scheduling?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8
    spring.datasource.master.username=root
    spring.datasource.master.password=123456
    
    # slave
    spring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.slave.url=jdbc:mysql://10.0.1.243:3306/scheduling1?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8
    spring.datasource.slave.username=root
    spring.datasource.slave.password=123456
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    自定义注解

    // 标记注解可使用在方法与类上
    @Target({ElementType.METHOD,ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DS {
    	// 默认值为MASTER
        String value() default DataSourceConstants.DS_KEY_MASTER;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    编写DataSourceConstants

    /**
     * 数据源常量
     **/
    public class DataSourceConstants {
        /**
         * master数据源
         */
        public static final String DS_KEY_MASTER = "master";
        /**
         * slave数据源
         */
        public static final String DS_KEY_SLAVE = "slave";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    动态数据源名称上下文处理

    /**
     * 动态数据源名称上下文处理
     **/
    public class DynamicDataSourceContextHolder {
    
        /**
         * 动态数据源名称上下文
         */
        private static final ThreadLocal DATASOURCE_CONTEXT_KEY_HOLDER = new ThreadLocal<>();
    
        /**
         * 设置数据源
         * @param key
         */
        public static void setContextKey(String key){
            DATASOURCE_CONTEXT_KEY_HOLDER.set(key);
        }
    
        /**
         * 获取数据源名称
         * @return
         */
        public static String getContextKey(){
            String key = DATASOURCE_CONTEXT_KEY_HOLDER.get();
            return key == null?DataSourceConstants.DS_KEY_MASTER:key;
        }
    
        /**
         * 删除当前数据源名称
         */
        public static void removeContextKey(){
            DATASOURCE_CONTEXT_KEY_HOLDER.remove();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    获取当前动态数据源方法

    /**
     * 动态数据源
     **/
    public class DynamicDataSource extends AbstractRoutingDataSource {
    
        @Override
        protected Object determineCurrentLookupKey() {
            return DynamicDataSourceContextHolder.getContextKey();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    动态数据源配置

    /**
     * 动态数据源配置
     **/
    @EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
    @Configuration
    // 此处我们
    //@PropertySource("classpath:config/jdbc.properties")
    @MapperScan(basePackages = "me.mason.demo.dynamicdatasource.mapper")
    public class DynamicDataSourceConfig {
        @Bean(DataSourceConstants.DS_KEY_MASTER)
        // 需要与配置文件中对应
        @ConfigurationProperties(prefix = "spring.datasource.master")
        public DruidDataSource masterDataSource() {
            return DruidDataSourceBuilder.create().build();
        }
    
        @Bean(DataSourceConstants.DS_KEY_SLAVE)
        @ConfigurationProperties(prefix = "spring.datasource.slave")
        public DruidDataSource slaveDataSource() {
            return DruidDataSourceBuilder.create().build();
        }
    
        @Bean
        @Primary
        public DynamicDataSource dynamicDataSource() {
            Map dataSourceMap = new HashMap<>(2);
            dataSourceMap.put(DataSourceConstants.DS_KEY_MASTER, masterDataSource());
            dataSourceMap.put(DataSourceConstants.DS_KEY_SLAVE, slaveDataSource());
            //设置动态数据源
            DynamicDataSource dynamicDataSource = new DynamicDataSource();
            dynamicDataSource.setTargetDataSources(dataSourceMap);
            dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
            return dynamicDataSource;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    AOP切面

    /**
     * 切面
     */
    @Aspect
    @Component
    //@Order(-10)
    public class DynamicDataSourceAspect {
    	// 以在类上使用了@Service作为切入点
        @Pointcut("@within(org.springframework.stereotype.Service)")
        public void dataSourcePointCut() {
        }
    
        @Around("dataSourcePointCut()")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Class aClass = Class.forName(signature.getDeclaringType().getName());
            // 方法优先,如果方法上存在注解,则优先使用方法上的注解
            if (signature.getMethod().isAnnotationPresent(DS.class)) { DynamicDataSourceContextHolder.setContextKey(signature.getMethod().getAnnotation(DS.class).value());
                // 其次类优先,如果类上存在注解,则使用类上的注解
            }else  if (aClass.isAnnotationPresent(DS.class)) { DynamicDataSourceContextHolder.setContextKey(aClass.getAnnotation(DS.class).value());
                // 如果都不存在,则使用默认
            }   else {
     DynamicDataSourceContextHolder.setContextKey(DataSourceConstants.DS_KEY_MASTER);
            }
            try {
                return joinPoint.proceed();
            } finally {
                DynamicDataSourceContextHolder.removeContextKey();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    编写TestUser实体

    @Data
    @TableName("test_user")
    public class TestUser implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        /** id */
        private Long id;
        /** 姓名 */
        private String name;
        /** 手机号 */
        private String phone;
        /** 职称职别 */
        private String title;
        /** 邮箱 */
        private String email;
        /** 性别 */
        private String gender;
        /** 出生时间 */
        private Date dateOfBirth;
        /** 1:已删除,0:未删除 */
        private Integer deleted;
        /** 创建时间 */
        private Date sysCreateTime;
        /** 创建人 */
        private String sysCreateUser;
        /** 更新时间 */
        private Date sysUpdateTime;
        /** 更新人 */
        private String sysUpdateUser;
        /** 版本号 */
        private Long recordVersion;
    
        public TestUser() {
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    TestUserMapper

    @Repository
    public interface TestUserMapper extends BaseMapper {
    
        /**
         * 自定义查询
         * @param wrapper 条件构造器
         * @return
         */
        List selectAll(@Param(Constants.WRAPPER) Wrapper wrapper);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    TestUserService

    @Service
    //@DS(DataSourceConstants.DS_KEY_SLAVE)
    public class TestUserService {
        @Autowired
        private TestUserMapper testUserMapper;
    
        /**
         * 查询master库User
         * @return
         */
    //    @DS(DataSourceConstants.DS_KEY_MASTER)
        public List getMasterUser(){
            QueryWrapper queryWrapper = new QueryWrapper<>();
            return testUserMapper.selectAll(queryWrapper.isNotNull("name"));
        }
    
        /**
         * 查询slave库User
         * @return
         */
    //    @DS(DataSourceConstants.DS_KEY_MASTER)
        public List getSlaveUser(){
            return testUserMapper.selectList(null);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    TestUserController

    @RestController
    @RequestMapping("/user")
    public class TestUserController {
        @Autowired
        private TestUserService testUserService;
        /**
         * 查询全部
         */
        @GetMapping("/listall")
        public Object listAll() {
            int initSize = 2;
            Map result = new HashMap<>(initSize);
            List masterUser = testUserService.getMasterUser();
            result.put("masterUser", masterUser);
            List slaveUser = testUserService.getSlaveUser();
            result.put("getSlaveUser", slaveUser);
            return ResponseResult.success(result);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    MapperXml

    
    
    
        
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    启动测试

    不使用注解
    @Service
    //@DS(DataSourceConstants.DS_KEY_SLAVE)
    public class TestUserService {
        @Autowired
        private TestUserMapper testUserMapper;
    
    
        /**
         * 查询master库User
         * @return
         */
    //    @DS(DataSourceConstants.DS_KEY_MASTER)
        public List getMasterUser(){
            QueryWrapper queryWrapper = new QueryWrapper<>();
            return testUserMapper.selectAll(queryWrapper.isNotNull("name"));
        }
    
        /**
         * 查询slave库User
         * @return
         */
    //    @DS(DataSourceConstants.DS_KEY_MASTER)
        public List getSlaveUser(){
            return testUserMapper.selectList(null);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    效果

    该代码优先级与使用框架效果一致,即不使用注解将默认使用MASTER数据库,方法上存在注解优先使用方法上标注的注解。

    已知MASTER 6条数据, SLAVE4条数据

    访问 http://127.0.0.1:8080/dd/user/listall 查看效果

    在这里插入图片描述

    类上使用注解
    @Service
    @DS(DataSourceConstants.DS_KEY_SLAVE)
    public class TestUserService {
        @Autowired
        private TestUserMapper testUserMapper;
    
    
        /**
         * 查询master库User
         * @return
         */
    //    @DS(DataSourceConstants.DS_KEY_MASTER)
        public List getMasterUser(){
            QueryWrapper queryWrapper = new QueryWrapper<>();
            return testUserMapper.selectAll(queryWrapper.isNotNull("name"));
        }
    
        /**
         * 查询slave库User
         * @return
         */
    //    @DS(DataSourceConstants.DS_KEY_MASTER)
        public List getSlaveUser(){
            return testUserMapper.selectList(null);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    效果

    在这里插入图片描述

    方法上使用注解
    @Service
    @DS(DataSourceConstants.DS_KEY_SLAVE)
    public class TestUserService {
        @Autowired
        private TestUserMapper testUserMapper;
        /**
         * 查询master库User
         * @return
         */
        @DS(DataSourceConstants.DS_KEY_SLAVE)
        public List getMasterUser(){
            QueryWrapper queryWrapper = new QueryWrapper<>();
            return testUserMapper.selectAll(queryWrapper.isNotNull("name"));
        }
        /**
         * 查询slave库User
         * @return
         */
        @DS(DataSourceConstants.DS_KEY_MASTER)
        public List getSlaveUser(){
            return testUserMapper.selectList(null);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    效果

    在这里插入图片描述

    先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

  • 相关阅读:
    Ubuntu20.04安装搜狗输入法
    基于java web个人财务管理系统
    报告解读:云原生落地传统行业的必然性
    TsinghuaDatabaseGroup - AIDB
    不知道10年老电脑如何重装系统?其实很简单
    LeetCode 2251. 花期内花的数目:排序 + 二分
    IBM Spectrum LSF Suite
    规则引擎Drools使用,0基础入门规则引擎Drools(一)基础入门
    人工神经网络案例分析题,人工神经网络算法实例
    React Promise 中断
  • 原文地址:https://blog.csdn.net/m0_67401761/article/details/126114612