当前位置:   article > 正文

多数据源组件dynamic-datasource使用总结

dynamic-datasource

简介

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

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

特性

  • 支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。
  • 支持数据库敏感配置信息 加密(可自定义) 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的分布式事务方案 。
  • 提供 本地多数据源事务方案。

快速入门 

第一步:pom.xml  引入dynamic-datasource-spring-boot-starter、

  1. <dependency>
  2. <groupId>com.baomidou</groupId>
  3. <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
  4. <version>3.5.1</version>
  5. </dependency>

第二步:application.yml 配置多数据源

  1. spring:
  2. datasource:
  3. dynamic:
  4. primary: master #设置默认的数据源或者数据源组,默认值即为master
  5. strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
  6. datasource:
  7. master:
  8. url: jdbc:mysql://192.168.43.10:3306/bill?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&autoReconnect=true
  9. username: root
  10. password: 123456
  11. driver-class-name: com.mysql.cj.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
  12. slave_1:
  13. url: jdbc:mysql://192.168.43.10:3306/usc?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&autoReconnect=true
  14. username: root
  15. password: 123456
  16. driver-class-name: com.mysql.cj.jdbc.Driver

第三步:使用@DS注解,切换数据源

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

注解结果
没有@DS默认数据源
@DS("dsName")dsName可以为组名也可以为具体某个库的名称

连接池集成 

在application.yml 配置文件中添加Hikari数据库连接池配置。

  1. spring:
  2. datasource:
  3. dynamic:
  4. primary: master #设置默认的数据源或者数据源组,默认值即为master
  5. strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
  6. datasource:
  7. master:
  8. url: jdbc:mysql://192.168.43.10:3306/bill?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&autoReconnect=true
  9. username: root
  10. password: 123456
  11. driver-class-name: com.mysql.cj.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
  12. slave_1:
  13. url: jdbc:mysql://192.168.43.10:3306/usc?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&autoReconnect=true
  14. username: root
  15. password: 123456
  16. driver-class-name: com.mysql.cj.jdbc.Driver
  17. hikari:
  18. minimum-idle: 10
  19. maximum-pool-size: 20
  20. idle-timeout: 500000
  21. max-lifetime: 540000
  22. connection-timeout: 60000
  23. connection-test-query: select 1

dynamic-datasource 支持如下数据库连接池:beecp\dbcp2\druid\hikair 等常用数据库连接池。

Beecp 通用配置:

  1. beecp:
  2. fairness: true
  3. initial-size: 5
  4. max-active: 20
  5. idleTimeout: 60000
  6. connectionTestSql: SELECT 1

dbcp2 通用配置:

  1. dbcp2:
  2. initial-size:5
  3. min-idle:5
  4. max-idle:10
  5. max-open-prepared-statements:100

druid 通用配置:

  1. druid:
  2. # 初始化大小
  3. initial-size: 5
  4. # 最小连接数
  5. min-idle: 10
  6. # 最大连接数
  7. max-active: 20
  8. # 获取连接时的最大等待时间
  9. max-wait: 60000
  10. # 一个连接在池中最小生存的时间,单位是毫秒
  11. min-evictable-idle-time-millis: 300000
  12. # 多久才进行一次检测需要关闭的空闲连接,单位是毫秒
  13. time-between-eviction-runs-millis: 60000
  14. # 配置扩展插件:stat-监控统计,log4j-日志,wall-防火墙(防止SQL注入),去掉后,监控界面的sql无法统计
  15. filters: stat,wall
  16. # 检测连接是否有效的 SQL语句,为空时以下三个配置均无效
  17. validation-query: SELECT 1
  18. # 申请连接时执行validationQuery检测连接是否有效,默认true,开启后会降低性能
  19. test-on-borrow: true
  20. # 归还连接时执行validationQuery检测连接是否有效,默认false,开启后会降低性能
  21. test-on-return: true
  22. # 申请连接时如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效,默认false,建议开启,不影响性能
  23. test-while-idle: true
  24. # 是否开启 StatViewServlet
  25. stat-view-servlet:
  26. enabled: true
  27. # 访问监控页面 白名单,默认127.0.0.1
  28. allow: 127.0.0.1
  29. login-username: admin
  30. login-password: admin
  31. # FilterStat
  32. filter:
  33. stat:
  34. # 是否开启 FilterStat,默认true
  35. enabled: true
  36. # 是否开启 慢SQL 记录,默认false
  37. log-slow-sql: true
  38. # 慢 SQL 的标准,默认 3000,单位:毫秒
  39. slow-sql-millis: 5000
  40. # 合并多个连接池的监控数据,默认false
  41. merge-sql: false

hikair 通用配置:

  1. hikari:
  2. minimum-idle: 10
  3. maximum-pool-size: 20
  4. idle-timeout: 500000
  5. max-lifetime: 540000
  6. connection-timeout: 60000
  7. connection-test-query: select 1

温馨提示:上述配置对象属性值与 dynamic-datasource定义的属性值对象可能存在差异,请以:

com.baomidou.dynamic.datasource.spring.boot.autoconfigure.* 相关配置类对象为主 

验证 dynamic-datasource 数据库连接池是否生效。

在任意类中依赖DataSource 接口类,检查输出的接口实例类是否为:com.baomidou.dynamic.datasource.DynamicRoutingDataSource。

示例代码:

  1. package com.zzg.controller;
  2. import com.zzg.entity.BaseProjectPO;
  3. import com.zzg.service.IBaseProjectService;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.PathVariable;
  7. import org.springframework.web.bind.annotation.RequestMapping;
  8. import org.springframework.web.bind.annotation.RestController;
  9. import javax.sql.DataSource;
  10. import java.util.List;
  11. @RestController
  12. @RequestMapping("/baseProject")
  13. public class BaseProjectController {
  14. @Autowired
  15. private DataSource dataSource;
  16. @Autowired
  17. private IBaseProjectService service;
  18. @GetMapping("/list")
  19. public List<BaseProjectPO> list() {
  20. System.out.println(dataSource.getClass());
  21. return service.list();
  22. }
  23. @GetMapping("{id}")
  24. public BaseProjectPO getBaseProjectInfo(@PathVariable String id){
  25. return service.getById(id);
  26. }
  27. }

在list 逻辑方法中,输出DataSource 接口实例对象为:com.baomidou.dynamic.datasource.DynamicRoutingDataSource

第三方集成 /MyBatis-plus

在application.yml 和Application 程序入口添加如下配置:

application.yml

  1. mybatis-plus:
  2. mapper-locations: classpath:mapper/*.xml
  3. type-aliases-package: cn.zzg.entity
Application
  1. package com.zzg;
  2. import org.mybatis.spring.annotation.MapperScan;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. @SpringBootApplication
  6. @MapperScan({"com.zzg.mapper"})
  7. public class Application {
  8. public static void main(String[] args) {
  9. SpringApplication.run(Application.class, args);
  10. }
  11. }

进阶使用

动态添加和移除数据源

示例:

1、定义数据源Model

  1. import io.swagger.annotations.ApiModelProperty;
  2. import lombok.Data;
  3. import javax.validation.constraints.NotBlank;
  4. @Data
  5. public class DataSourceDTO {
  6. @NotBlank
  7. @ApiModelProperty(value = "连接池名称", example = "db1")
  8. private String poolName;
  9. @NotBlank
  10. @ApiModelProperty(value = "JDBC driver", example = "com.mysql.cj.jdbc.Driver")
  11. private String driverClassName;
  12. @NotBlank
  13. @ApiModelProperty(value = "JDBC url 地址", example = "jdbc:mysql://x.x.x.x:3306/x?useUnicode=true&characterEncoding=utf-8")
  14. private String url;
  15. @NotBlank
  16. @ApiModelProperty(value = "JDBC 用户名", example = "sa")
  17. private String username;
  18. @ApiModelProperty(value = "JDBC 密码")
  19. private String password;
  20. }

2、Controller 定义添加和移除数据源

  1. import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
  2. import com.baomidou.dynamic.datasource.creator.*;
  3. import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
  4. import com.zzg.common.DataSourceDTO;
  5. import io.swagger.annotations.Api;
  6. import io.swagger.annotations.ApiOperation;
  7. import org.springframework.beans.BeanUtils;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.validation.annotation.Validated;
  10. import org.springframework.web.bind.annotation.*;
  11. import javax.sql.DataSource;
  12. import java.util.Set;
  13. @RestController
  14. @RequestMapping("/datasources")
  15. @Api(tags = "添加删除数据源")
  16. public class DataSourceController {
  17. @Autowired
  18. private DataSource dataSource;
  19. @Autowired
  20. private DefaultDataSourceCreator dataSourceCreator;
  21. @Autowired
  22. private DruidDataSourceCreator druidDataSourceCreator;
  23. @Autowired
  24. private HikariDataSourceCreator hikariDataSourceCreator;
  25. @GetMapping
  26. @ApiOperation("获取当前所有数据源")
  27. public Set<String> now() {
  28. DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
  29. return ds.getDataSources().keySet();
  30. }
  31. //通用数据源会根据maven中配置的连接池根据顺序依次选择。
  32. //默认的顺序为druid>hikaricp>beecp>dbcp>spring basic
  33. @PostMapping("/add")
  34. @ApiOperation("通用添加数据源(推荐)")
  35. public Set<String> add(@Validated @RequestBody DataSourceDTO dto) {
  36. DataSourceProperty dataSourceProperty = new DataSourceProperty();
  37. BeanUtils.copyProperties(dto, dataSourceProperty);
  38. DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
  39. DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty);
  40. ds.addDataSource(dto.getPoolName(), dataSource);
  41. return ds.getDataSources().keySet();
  42. }
  43. @PostMapping("/addDruid")
  44. @ApiOperation("基础Druid数据源")
  45. public Set<String> addDruid(@Validated @RequestBody DataSourceDTO dto) {
  46. DataSourceProperty dataSourceProperty = new DataSourceProperty();
  47. BeanUtils.copyProperties(dto, dataSourceProperty);
  48. dataSourceProperty.setLazy(true);
  49. DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
  50. DataSource dataSource = druidDataSourceCreator.createDataSource(dataSourceProperty);
  51. ds.addDataSource(dto.getPoolName(), dataSource);
  52. return ds.getDataSources().keySet();
  53. }
  54. @PostMapping("/addHikariCP")
  55. @ApiOperation("基础HikariCP数据源")
  56. public Set<String> addHikariCP(@Validated @RequestBody DataSourceDTO dto) {
  57. DataSourceProperty dataSourceProperty = new DataSourceProperty();
  58. BeanUtils.copyProperties(dto, dataSourceProperty);
  59. dataSourceProperty.setLazy(true);//3.4.0版本以下如果有此属性,需手动设置,不然会空指针。
  60. DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
  61. DataSource dataSource = hikariDataSourceCreator.createDataSource(dataSourceProperty);
  62. ds.addDataSource(dto.getPoolName(), dataSource);
  63. return ds.getDataSources().keySet();
  64. }
  65. @DeleteMapping
  66. @ApiOperation("删除数据源")
  67. public String remove(String name) {
  68. DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
  69. ds.removeDataSource(name);
  70. return "删除成功";
  71. }
  72. }

数据库加密

第一步:使用框架自带加密工具类com.baomidou.dynamic.datasource.toolkit.CryptoUtils对需要加密的字符串进行加密。

  1. @Test
  2. void test() throws Exception {
  3. String passWord= CryptoUtils.encrypt("123456");
  4. System.out.println(passWord);
  5. // 输出加密密码
  6. //Y3ycHCcZGa+N+OK+qXTWA0gJ1L1N+FYrswTgRQEegdKVTefiujYxjlytR6zOuV5Y3AifL/P10yWshYKQaqpkkQ==
  7. }

第二步:修改配置文件中的相关密码

  1. server:
  2. port: 8080
  3. spring:
  4. datasource:
  5. dynamic:
  6. primary: master #设置默认的数据源或者数据源组,默认值即为master
  7. strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
  8. datasource:
  9. master:
  10. url: jdbc:mysql://192.168.43.10:3306/bill?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&autoReconnect=true
  11. username: root
  12. password: ENC(Y3ycHCcZGa+N+OK+qXTWA0gJ1L1N+FYrswTgRQEegdKVTefiujYxjlytR6zOuV5Y3AifL/P10yWshYKQaqpkkQ==)
  13. driver-class-name: com.mysql.cj.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
  14. slave_1:
  15. url: jdbc:mysql://192.168.43.10:3306/usc?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&autoReconnect=true
  16. username: root
  17. password: ENC(Y3ycHCcZGa+N+OK+qXTWA0gJ1L1N+FYrswTgRQEegdKVTefiujYxjlytR6zOuV5Y3AifL/P10yWshYKQaqpkkQ==)
  18. driver-class-name: com.mysql.cj.jdbc.Driver
  19. hikari:
  20. minimum-idle: 10
  21. maximum-pool-size: 20
  22. idle-timeout: 500000
  23. max-lifetime: 540000
  24. connection-timeout: 60000
  25. connection-test-query: select 1
  26. mybatis-plus:
  27. mapper-locations: classpath:mapper/*.xml
  28. type-aliases-package: cn.zzg.entity

自动读写分离

通过上面的学习,我们已经学习了解到了@DS 注解,基于此注解我们可以快速实现简单版本的读写分离。

  1. package com.zzg.mapper;
  2. import com.baomidou.dynamic.datasource.annotation.DS;
  3. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  4. import com.zzg.entity.BaseArch;
  5. import org.apache.ibatis.annotations.Insert;
  6. import org.apache.ibatis.annotations.Mapper;
  7. import org.apache.ibatis.annotations.Param;
  8. import org.apache.ibatis.annotations.Select;
  9. @Mapper
  10. public interface BaseArchMapper extends BaseMapper<BaseArch> {
  11. @DS("master")
  12. int addUser(BaseArch entity);
  13. @DS("slave_1")
  14. BaseArch findUser(@Param("id") String id);
  15. }

不过这种方式有点繁琐,每个Mapper都需要添加注解。我们 可以通过MyBatis 拦截器 + 手动切换数据源实现读写分离。

定义:读写分离MyBatis 拦截器

  1. @Intercepts({@Signature(
  2. type = Executor.class,
  3. method = "query",
  4. args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
  5. ), @Signature(
  6. type = Executor.class,
  7. method = "query",
  8. args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
  9. ), @Signature(
  10. type = Executor.class,
  11. method = "update",
  12. args = {MappedStatement.class, Object.class}
  13. )})
  14. @Component
  15. @Primary
  16. public class MasterSlaveAutoRoutingPlugin implements Interceptor {
  17. private static final String MASTER = "master";
  18. private static final String SLAVE = "slave_1";
  19. @Override
  20. public Object intercept(Invocation invocation) throws Throwable {
  21. Object[] args = invocation.getArgs();
  22. MappedStatement ms = (MappedStatement) args[0];
  23. try {
  24. DynamicDataSourceContextHolder.push(SqlCommandType.SELECT == ms.getSqlCommandType() ? SLAVE : MASTER);
  25. return invocation.proceed();
  26. } finally {
  27. DynamicDataSourceContextHolder.clear();
  28. }
  29. }
  30. @Override
  31. public Object plugin(Object target) {
  32. return target instanceof Executor ? Plugin.wrap(target, this) : target;
  33. }
  34. @Override
  35. public void setProperties(Properties properties) {
  36. }
  37. }

至此,我们可以移除Controller/Service/Dao/Mapper 上得@DS 注解。

手动切换数据源

请参考文章:多数据源切换[dynamic-datasource] 手动切换数据源

负载均衡 

通过 mybatis 的拦截器 + 手动切换数据源实现了读写分离,同时 dynamic-datasource 还为我们提供了负载的效果,同一个组下的默认就是负载均衡效果,怎么才是同一个组呢,上面有提到只需以下划线 _ 分割即可,下面修改配置文件:

  1. spring:
  2. datasource:
  3. dynamic:
  4. primary: db_1 #设置默认的数据源或者数据源组,默认值即为master
  5. strict: true #设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候回抛出异常,不启动会使用默认数据源.
  6. datasource:
  7. db_1:
  8. driver-class-name: com.mysql.cj.jdbc.Driver
  9. type: com.alibaba.druid.pool.DruidDataSource
  10. url: jdbc:mysql://192.168.43.10:3306/db1?useUnicode=true&characterEncoding=utf8
  11. username: root
  12. password: 123456
  13. db_2:
  14. driver-class-name: com.mysql.cj.jdbc.Driver
  15. type: com.alibaba.druid.pool.DruidDataSource
  16. url: jdbc:mysql://192.168.43.10:3306/db2?useUnicode=true&characterEncoding=utf8
  17. username: root
  18. password: 123456

声明 Dao ,指定数据源为 db :

  1. @Mapper
  2. @DS("db")
  3. public interface UserMapper extends BaseMapper<UserEntity> {
  4. }

 事务管理

大家都了解在 Spring 中事物使用 @Transactional 注解即可,但是仅针对于单个数据源的情况,多数据源下我们可以使用 jta 来控制或其他事务管理框架,在 dynamic-datasource 中又推出了 @DSTransactional 注解来代替 Spring 的 @Transactional 注解。

  1. @Slf4j
  2. @SpringBootTest
  3. class DynamicDatasourceDemoApplicationTests {
  4. @Autowired
  5. DB1UserDao db1UserDao;
  6. @Autowired
  7. DB2UserDao db2UserDao;
  8. @Test
  9. @DSTransactional
  10. void test1() {
  11. UserEntity entity = new UserEntity();
  12. entity.setName("王五");
  13. entity.setAge(16);
  14. int db1 = db1UserDao.insert(entity);
  15. log.info("db1写入个数:{} ", db1);
  16. int db2 = db2UserDao.insert(entity);
  17. log.info("db2写入个数:{} ", db2);
  18. //模拟异常
  19. int a = 1 / 0;
  20. }
  21. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/677919
推荐阅读
相关标签
  

闽ICP备14008679号