利用SpringBoot实现多数据源的两种方式总结

目录

  • 前言
  • 基于dynamic-datasource实现多数据源
    • dynamic-datasource介绍
    • dynamic-datasource的相关约定
    • 引入dynamic-datasource依赖
    • 配置数据源
    • 使用 @DS 切换数据源
  • 基于AOP手动实现多数据源
    • 项目工程结构
    • 项目依赖
    • 配置文件
    • 自定义注解
    • 编写DataSourceConstants
    • 动态数据源名称上下文处理
    • 获取当前动态数据源方法
    • 动态数据源配置
    • AOP切面
    • 编写TestUser实体
    • TestUserMapper
    • TestUserService
    • TestUserController
    • MapperXml
    • 启动测试
    • 效果
  • 总结

    前言 公司项目有连接多个不同数据库的需求,特研究了一下,根据网上的资料,造了一个基于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的相关约定
    • dynamic-datasource只做 切换数据源 这件核心的事情,并不限制你的具体操作,切换了数据源可以做任何CRUD。
    • 配置文件所有以下划线 _ 分割的数据源 首部 即为组的名称,相同组名称的数据源会放在一个组下。
    • 切换数据源可以是组名,也可以是具体数据源名称。组名则切换时采用负载均衡算法切换。
    • 默认的数据源名称为 master ,你可以通过 spring.datasource.dynamic.primary 修改。
    • 方法上的注解优先于类上注解。
    • DS支持继承抽象类上的DS,暂不支持继承接口上的DS。

    引入dynamic-datasource依赖
    com.baomidoudynamic-datasource-spring-boot-starter${version}


    配置数据源
    spring:datasource:dynamic:primary: mysql #设置默认的数据源或者数据源组,默认值即为masterstrict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源datasource:mysql:url: jdbc:mysql://xx.xx.xx.xx:3306/dynamicusername: rootpassword: 123456driver-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


    使用 @DS 切换数据源
    @DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解。
    注解 结果
    不使用@DS注解 默认数据源,即primary: mysql
    @DS(“dsName”) dsName可以为组名也可以为具体某个库的名称
    @DS使用实例
    @Service@DS("mysql")public class UserServiceImpl implements UserService {@Autowiredprivate JdbcTemplate jdbcTemplate; // 不使用@DS注解则代表使用默认数据源// 如果类上存在,则使用类上标注的数据源public List selectAll() {returnjdbcTemplate.queryForList("select * from user"); }@Override@DS("pgsql")// 方法上注解 优先于 类上注解,即使类上标注也优先采用方法上的标注public List selectByCondition() {returnjdbcTemplate.queryForList("select * from user where age >10"); }}

    【利用SpringBoot实现多数据源的两种方式总结】
    基于AOP手动实现多数据源 本次代码参考 https://github.com/mianshenglee/my-example/tree/master/multi-datasource/dynamic-datasource ,因源码不满足的需求,因此我在此基础做了修改。

    项目工程结构
    利用SpringBoot实现多数据源的两种方式总结
    文章图片


    项目依赖
    4.0.0org.springframework.bootspring-boot-starter-parent2.2.2.RELEASE me.mason.demodynamic-datasource0.0.1-SNAPSHOTdynamic-datasourceDemo project for dynamic datasource1.8org.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-configuration-processorcom.alibabadruid-spring-boot-starter1.1.9org.springframework.bootspring-boot-starter-jdbcorg.springframework.bootspring-boot-starter-aopmysqlmysql-connector-javaruntimecom.baomidoumybatis-plus-boot-starter3.3.0org.projectlomboklomboktrueorg.springframework.bootspring-boot-starter-testtestorg.junit.vintagejunit-vintage-engineorg.springframework.bootspring-boot-maven-plugin


    配置文件
    server.port=8080server.servlet.context-path=/ddlogging.level.root=INFOlogging.level.me.mason.demo.dynamicdatasource.mapper=DEBUG# mybatis-plusmybatis-plus.type-aliases-package=me.mason.demo.dynamicdatasource.entity# 默认位置,可不配置#mybatis-plus.mapper-locations=classpath*:/mapper/*.xmlmybatis.mapper-locations=classpath*:/mapper/*.xml# 使用数据库自增IDmybatis-plus.global-config.db-config.id-type=auto# masterspring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.master.url=jdbc:mysql://10.0.1.243:3306/scheduling?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8spring.datasource.master.username=rootspring.datasource.master.password=123456# slavespring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.slave.url=jdbc:mysql://10.0.1.243:3306/scheduling1?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8spring.datasource.slave.username=rootspring.datasource.slave.password=123456


    自定义注解
    // 标记注解可使用在方法与类上@Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface DS { // 默认值为MASTERString value() default DataSourceConstants.DS_KEY_MASTER; }


    编写DataSourceConstants
    /** * 数据源常量 **/public class DataSourceConstants {/*** master数据源*/public static final String DS_KEY_MASTER = "master"; /*** slave数据源*/public static final String DS_KEY_SLAVE = "slave"; }


    动态数据源名称上下文处理
    /** * 动态数据源名称上下文处理 **/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(); }}


    获取当前动态数据源方法
    /** * 动态数据源 **/public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getContextKey(); }}


    动态数据源配置
    /** * 动态数据源配置 **/@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@Primarypublic 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; }}


    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()); // 其次类优先,如果类上存在注解,则使用类上的注解}elseif (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(); }}}


    编写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() {}}


    TestUserMapper
    @Repositorypublic interface TestUserMapper extends BaseMapper {/*** 自定义查询* @param wrapper 条件构造器* @return*/List selectAll(@Param(Constants.WRAPPER) Wrapper wrapper); }


    TestUserService
    @Service//@DS(DataSourceConstants.DS_KEY_SLAVE)public class TestUserService {@Autowiredprivate 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); }}


    TestUserController
    @RestController@RequestMapping("/user")public class TestUserController {@Autowiredprivate 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); }}


    MapperXml
    select * from test_user${ew.customSqlSegment}


    启动测试
    不使用注解
    @Service//@DS(DataSourceConstants.DS_KEY_SLAVE)public class TestUserService {@Autowiredprivate 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); }}


    效果
    该代码优先级与使用框架效果一致,即不使用注解将默认使用MASTER数据库,方法上存在注解优先使用方法上标注的注解。
    已知MASTER 6条数据, SLAVE4条数据
    访问 http://127.0.0.1:8080/dd/user/listall 查看效果
    利用SpringBoot实现多数据源的两种方式总结
    文章图片

    类上使用注解
    @Service@DS(DataSourceConstants.DS_KEY_SLAVE)public class TestUserService {@Autowiredprivate 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); }}

    效果
    利用SpringBoot实现多数据源的两种方式总结
    文章图片

    方法上使用注解
    @Service@DS(DataSourceConstants.DS_KEY_SLAVE)public class TestUserService {@Autowiredprivate 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); }}

    效果
    利用SpringBoot实现多数据源的两种方式总结
    文章图片


    总结 到此这篇关于利用SpringBoot实现多数据源的两种方式的文章就介绍到这了,更多相关SpringBoot实现多数据源内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

      推荐阅读