经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
SpringBoot多数据源的两种实现方式实例
来源:jb51  时间:2022/4/18 12:05:12  对本文有异议

前言

公司项目有连接多个不同数据库的需求,特研究了一下,根据网上的资料,造了一个基于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依赖

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

配置数据源

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

使用 @DS 切换数据源

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

注解结果
不使用@DS注解默认数据源,即primary: mysql
@DS(“dsName”)dsName可以为组名也可以为具体某个库的名称

@DS使用实例

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

基于AOP手动实现多数据源

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

项目工程结构

项目依赖

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.2.2.RELEASE</version>
  9. <relativePath/> <!-- lookup parent from repository -->
  10. </parent>
  11. <groupId>me.mason.demo</groupId>
  12. <artifactId>dynamic-datasource</artifactId>
  13. <version>0.0.1-SNAPSHOT</version>
  14. <name>dynamic-datasource</name>
  15. <description>Demo project for dynamic datasource</description>
  16.  
  17. <properties>
  18. <java.version>1.8</java.version>
  19. </properties>
  20.  
  21. <dependencies>
  22. <!--spring boot-->
  23. <dependency>
  24. <groupId>org.springframework.boot</groupId>
  25. <artifactId>spring-boot-starter-web</artifactId>
  26. </dependency>
  27. <dependency>
  28. <groupId>org.springframework.boot</groupId>
  29. <artifactId>spring-boot-configuration-processor</artifactId>
  30. </dependency>
  31. <dependency>
  32. <groupId>com.alibaba</groupId>
  33. <artifactId>druid-spring-boot-starter</artifactId>
  34. <version>1.1.9</version>
  35. </dependency>
  36. <dependency>
  37. <groupId>org.springframework.boot</groupId>
  38. <artifactId>spring-boot-starter-jdbc</artifactId>
  39. </dependency>
  40. <dependency>
  41. <groupId>org.springframework.boot</groupId>
  42. <artifactId>spring-boot-starter-aop</artifactId>
  43. </dependency>
  44. <!--mysql 驱动-->
  45. <dependency>
  46. <groupId>mysql</groupId>
  47. <artifactId>mysql-connector-java</artifactId>
  48. <scope>runtime</scope>
  49. </dependency>
  50. <!--mybatis-plus-->
  51. <dependency>
  52. <groupId>com.baomidou</groupId>
  53. <artifactId>mybatis-plus-boot-starter</artifactId>
  54. <version>3.3.0</version>
  55. </dependency>
  56. <dependency>
  57. <groupId>org.projectlombok</groupId>
  58. <artifactId>lombok</artifactId>
  59. <optional>true</optional>
  60. </dependency>
  61. <dependency>
  62. <groupId>org.springframework.boot</groupId>
  63. <artifactId>spring-boot-starter-test</artifactId>
  64. <scope>test</scope>
  65. <exclusions>
  66. <exclusion>
  67. <groupId>org.junit.vintage</groupId>
  68. <artifactId>junit-vintage-engine</artifactId>
  69. </exclusion>
  70. </exclusions>
  71. </dependency>
  72.  
  73. </dependencies>
  74.  
  75. <build>
  76. <plugins>
  77. <plugin>
  78. <groupId>org.springframework.boot</groupId>
  79. <artifactId>spring-boot-maven-plugin</artifactId>
  80. </plugin>
  81. </plugins>
  82. </build>
  83.  
  84. </project>

配置文件

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

自定义注解

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

编写DataSourceConstants

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

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

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

获取当前动态数据源方法

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

动态数据源配置

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

AOP切面

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

编写TestUser实体

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

TestUserMapper

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

TestUserService

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

TestUserController

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

MapperXml

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper
  3. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <mapper namespace="me.mason.demo.dynamicdatasource.mapper.TestUserMapper">
  6. <select id="selectAll" resultType="me.mason.demo.dynamicdatasource.entity.TestUser">
  7. select * from test_user
  8. <if test="ew!=null">
  9. ${ew.customSqlSegment}
  10. </if>
  11. </select>
  12. </mapper>

启动测试

不使用注解

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

效果

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

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

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

类上使用注解

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

效果

方法上使用注解

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

效果

总结

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

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号