• spring boot 整合多数据源


    数据源产生的场景

    一般情况下,不会有多数据源这样的场景出现,但老项目或者特殊需求的项目,可能会有这样的场景

    • 同一个应用需要访问两个数据库
    • 不用数据库中间件的读写分离

     注入数据源选择的时机

    声明两个数据源实例,在getConnection的时候根据业务的不同,注入不同数据源的连接

    环境准备

    准备sql脚本,建立两个库,这里mysql为例

    1. create database stu;
    2. create database tech;
    3. use stu;
    4. create table student
    5. (
    6. id varchar(50) not null comment '主键',
    7. name varchar(50) null comment '姓名',
    8. stu_no varchar(50) null comment '学号',
    9. constraint student_pk primary key (id)
    10. );
    11. insert into student values ('1','张同学','111');
    12. insert into student values ('2','李同学','222');
    13. use tech;
    14. create table teacher
    15. (
    16. id varchar(50) not null comment '主键',
    17. name varchar(50) null comment '姓名',
    18. teach_no varchar(50) null comment '教师号',
    19. constraint teacher_pk primary key (id)
    20. );
    21. insert into teacher values ('1','王老师','111');
    22. insert into teacher values ('2','高老师','222');

    实现DataSource方式实现多数据源

    配置多数据源

    1. server:
    2. port: 9000
    3. spring:
    4. datasource:
    5. type: com.alibaba.druid.pool.DruidDataSource
    6. datasource1:
    7. url: jdbc:mysql://shilei.tech:3306/stu?useSSL=true&serverTimezone=Asia/Shanghai
    8. username: root
    9. password: root123456
    10. driver-class-name: com.mysql.cj.jdbc.Driver
    11. datasource2:
    12. url: jdbc:mysql://shilei.tech:3306/tech?useSSL=true&serverTimezone=Asia/Shanghai
    13. username: root
    14. password: root123456
    15. driver-class-name: com.mysql.cj.jdbc.Driver
    16. druid:
    17. initial-size: 5
    18. min-idle: 1
    19. max-active: 20
    20. mybatis-plus:
    21. mapper-locations: classpath:/mapper/*.xml
    22. type-aliases-package: com.datasource.dynamicdatasource.model

    添加数据源配置

    1. package com.datasource.dynamicdatasource.config;
    2. import com.alibaba.druid.pool.DruidDataSource;
    3. import org.springframework.boot.context.properties.ConfigurationProperties;
    4. import org.springframework.context.annotation.Bean;
    5. import org.springframework.context.annotation.Configuration;
    6. import javax.sql.DataSource;
    7. /**
    8. * @author sl
    9. */
    10. @Configuration
    11. public class DataSourceConfig {
    12. @Bean("dataSource1")
    13. @ConfigurationProperties(prefix = "spring.datasource.datasource1")
    14. public DataSource dataSource1(){
    15. DruidDataSource druidDataSource = new DruidDataSource();
    16. return druidDataSource;
    17. }
    18. @Bean("dataSource2")
    19. @ConfigurationProperties(prefix = "spring.datasource.datasource2")
    20. public DataSource dataSource2(){
    21. DruidDataSource druidDataSource = new DruidDataSource();
    22. return druidDataSource;
    23. }
    24. }

     实现DataSource多数据源

    1. package com.datasource.dynamicdatasource.config;
    2. import org.springframework.beans.factory.InitializingBean;
    3. import org.springframework.context.annotation.Configuration;
    4. import org.springframework.context.annotation.Primary;
    5. import javax.annotation.Resource;
    6. import javax.sql.DataSource;
    7. import java.io.PrintWriter;
    8. import java.sql.Connection;
    9. import java.sql.SQLException;
    10. import java.sql.SQLFeatureNotSupportedException;
    11. import java.util.logging.Logger;
    12. /**
    13. * @author sl
    14. * @Primary主要注入的bean
    15. */
    16. @Configuration
    17. @Primary
    18. public class DynamicDataSource implements DataSource {
    19. public static ThreadLocal nameFlag = new ThreadLocal<>();
    20. @Resource
    21. private DataSource dataSource1;
    22. @Resource
    23. private DataSource dataSource2;
    24. @Override
    25. public Connection getConnection() throws SQLException {
    26. if("student".equals(nameFlag.get())){
    27. return dataSource1.getConnection();
    28. }
    29. return dataSource2.getConnection();
    30. }
    31. @Override
    32. public Connection getConnection(String username, String password) throws SQLException {
    33. return null;
    34. }
    35. @Override
    36. public T unwrap(Class iface) throws SQLException {
    37. return null;
    38. }
    39. @Override
    40. public boolean isWrapperFor(Class iface) throws SQLException {
    41. return false;
    42. }
    43. @Override
    44. public PrintWriter getLogWriter() throws SQLException {
    45. return null;
    46. }
    47. @Override
    48. public void setLogWriter(PrintWriter out) throws SQLException {
    49. }
    50. @Override
    51. public void setLoginTimeout(int seconds) throws SQLException {
    52. }
    53. @Override
    54. public int getLoginTimeout() throws SQLException {
    55. return 0;
    56. }
    57. @Override
    58. public Logger getParentLogger() throws SQLFeatureNotSupportedException {
    59. return null;
    60. }
    61. }

    测试多数据源

    1. package com.datasource.dynamicdatasource.controller;
    2. import com.datasource.dynamicdatasource.config.DynamicDataSource;
    3. import com.datasource.dynamicdatasource.model.Student;
    4. import com.datasource.dynamicdatasource.model.Teacher;
    5. import com.datasource.dynamicdatasource.service.StudentService;
    6. import com.datasource.dynamicdatasource.service.TeacherService;
    7. import org.springframework.beans.factory.annotation.Autowired;
    8. import org.springframework.web.bind.annotation.GetMapping;
    9. import org.springframework.web.bind.annotation.RestController;
    10. import java.util.List;
    11. /**
    12. * @author sl
    13. */
    14. @RestController
    15. public class TestDataSourceController {
    16. @Autowired
    17. private StudentService studentService;
    18. @Autowired
    19. private TeacherService teacherService;
    20. @GetMapping("/stu")
    21. public String getStu(){
    22. DynamicDataSource.nameFlag.set("student");
    23. List allStudent = studentService.findAllStudent();
    24. return allStudent.toString();
    25. }
    26. @GetMapping("/tech")
    27. public String getTech(){
    28. DynamicDataSource.nameFlag.set("teacher");
    29. List allTeacher = teacherService.findAllTeacher();
    30. return allTeacher.toString();
    31. }
    32. }

    效果如下所示

    此实现方式的弊端

    实现DataSource接口我们本质上只使用了一个方法,就是getConnection()这个无参的方法,其他方法,当内部调用时可能会导致错误,我们不可能实现所有的方法,所以我们继承AbstractRoutingDataSource抽象类

    继承AbstractRoutingDataSource实现多数据源

    AbstractRoutingDataSource的结构

    可以看到AbstractRoutingDataSource继承自DataSource,提供了一些实现方法

    AbstractRoutingDataSource的重要属性 

    targetDataSources 所有数据源 (需指定)

    defaultTargetDataSource 默认数据源(需指定)

    resolvedDataSources= targetDataSources 负责最终切换的数据源map 等于 tagetDataSources

    继承AbstractRoutingDataSource实现多数据源

    1. package com.datasource.dynamicdatasource.config;
    2. import org.springframework.context.annotation.Configuration;
    3. import org.springframework.context.annotation.Primary;
    4. import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    5. import javax.annotation.Resource;
    6. import javax.sql.DataSource;
    7. import java.util.HashMap;
    8. import java.util.Map;
    9. /**
    10. * @author sl
    11. * @Primary主要注入的bean
    12. */
    13. @Configuration
    14. @Primary
    15. public class DynamicDataSource extends AbstractRoutingDataSource {
    16. public static ThreadLocal nameFlag = new ThreadLocal<>();
    17. @Resource
    18. private DataSource dataSource1;
    19. @Resource
    20. private DataSource dataSource2;
    21. @Override
    22. protected Object determineCurrentLookupKey() {
    23. // 返回当前数据源的标识
    24. return nameFlag.get();
    25. }
    26. @Override
    27. public void afterPropertiesSet() {
    28. // 为targetDataSources 初始化所有数据源
    29. Map targetDataSources=new HashMap<>();
    30. targetDataSources.put("student",dataSource1);
    31. targetDataSources.put("teacher",dataSource2);
    32. super.setTargetDataSources(targetDataSources);
    33. // 设置默认数据源
    34. super.setDefaultTargetDataSource(dataSource1);
    35. // 循环给resolvedDataSources,也就是最终数据源map
    36. super.afterPropertiesSet();
    37. }
    38. }

    determineCurrentLookupKey的作用 

    看一段源码,就是通过determineCurrentLookupKey获取数据源的key,然后去resolvedDataSources中取数据源,resolvedDataSources数据源其实就是targetDataSources

    1. protected DataSource determineTargetDataSource() {
    2. Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
    3. Object lookupKey = determineCurrentLookupKey();
    4. DataSource dataSource = this.resolvedDataSources.get(lookupKey);
    5. if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
    6. dataSource = this.resolvedDefaultDataSource;
    7. }
    8. if (dataSource == null) {
    9. throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
    10. }
    11. return dataSource;
    12. }

     测试多数据源

    1. package com.datasource.dynamicdatasource.controller;
    2. import com.datasource.dynamicdatasource.config.DynamicDataSource;
    3. import com.datasource.dynamicdatasource.model.Student;
    4. import com.datasource.dynamicdatasource.model.Teacher;
    5. import com.datasource.dynamicdatasource.service.StudentService;
    6. import com.datasource.dynamicdatasource.service.TeacherService;
    7. import org.springframework.beans.factory.annotation.Autowired;
    8. import org.springframework.web.bind.annotation.GetMapping;
    9. import org.springframework.web.bind.annotation.RestController;
    10. import java.util.List;
    11. /**
    12. * @author sl
    13. */
    14. @RestController
    15. public class TestDataSourceController {
    16. @Autowired
    17. private StudentService studentService;
    18. @Autowired
    19. private TeacherService teacherService;
    20. @GetMapping("/stu")
    21. public String getStu(){
    22. // 默认数据源就是student
    23. List allStudent = studentService.findAllStudent();
    24. return allStudent.toString();
    25. }
    26. @GetMapping("/tech")
    27. public String getTech(){
    28. DynamicDataSource.nameFlag.set("teacher");
    29. List allTeacher = teacherService.findAllTeacher();
    30. return allTeacher.toString();
    31. }
    32. }

    AOP自定义注解方式+AbstractRoutingDataSource实现多数据源

    数据源的切换还是使用AbstractRoutingDataSource,只不过切换方式采用aop拦截自定义注解切换数据源,这种方式也是mybatis-plus多数据源插件所采用的方式

    自定义注解

    1. package com.datasource.dynamicdatasource.annotation;
    2. import java.lang.annotation.ElementType;
    3. import java.lang.annotation.Retention;
    4. import java.lang.annotation.RetentionPolicy;
    5. import java.lang.annotation.Target;
    6. /**
    7. * @author sl
    8. */
    9. @Target({ElementType.TYPE, ElementType.METHOD})
    10. @Retention(RetentionPolicy.RUNTIME)
    11. public @interface MyDataSource {
    12. String value() default "student";
    13. }

    配置切面

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-aopartifactId>
    4. dependency>
    1. package com.datasource.dynamicdatasource.aspect;
    2. import com.datasource.dynamicdatasource.annotation.MyDataSource;
    3. import com.datasource.dynamicdatasource.config.DynamicDataSource;
    4. import org.aspectj.lang.JoinPoint;
    5. import org.aspectj.lang.annotation.Aspect;
    6. import org.aspectj.lang.annotation.Before;
    7. import org.aspectj.lang.annotation.Pointcut;
    8. import org.aspectj.lang.reflect.MethodSignature;
    9. import org.springframework.stereotype.Component;
    10. import java.lang.reflect.Method;
    11. /**
    12. * @author sl
    13. *@Aspect 标识是一个切面
    14. */
    15. @Aspect
    16. @Component
    17. public class DatasourceAspect {
    18. /**
    19. * 切点规则
    20. */
    21. @Pointcut("@annotation(com.datasource.dynamicdatasource.annotation.MyDataSource)")
    22. public void pointcut() {
    23. }
    24. @Before("pointcut()")
    25. public void dataSourceAspect(JoinPoint joinPoint){
    26. // 获取方法
    27. Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
    28. // 判断方法中是否添加了注解
    29. if(method.isAnnotationPresent(MyDataSource.class)){
    30. // 获取方法上的注解
    31. MyDataSource annotation = method.getAnnotation(MyDataSource.class);
    32. String value = annotation.value();
    33. // 设置数据源
    34. DynamicDataSource.nameFlag.set(value);
    35. }
    36. }
    37. }

    测试自定义注解切换数据源

    1. @GetMapping("/tech")
    2. @MyDataSource("teacher")
    3. public String getTech(){
    4. List allTeacher = teacherService.findAllTeacher();
    5. return allTeacher.toString();
    6. }

    dynamic-datasource多数据源组件实现多数据源

    官方文档及搭建指南地址:多数据源 | MyBatis-Plus

    引入依赖

    1. <dependency>
    2. <groupId>com.baomidougroupId>
    3. <artifactId>dynamic-datasource-spring-boot-starterartifactId>
    4. <version>3.6.1version>
    5. dependency>

    配置数据源

    1. server:
    2. port: 9000
    3. spring:
    4. datasource:
    5. type: com.alibaba.druid.pool.DruidDataSource
    6. dynamic:
    7. #设置默认的数据源或者数据源组,默认值即为master
    8. primary: master
    9. #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
    10. strict: false
    11. datasource:
    12. master:
    13. url: jdbc:mysql://shilei.tech:3306/stu?useSSL=true&serverTimezone=Asia/Shanghai
    14. username: root
    15. password: root123456
    16. driver-class-name: com.mysql.cj.jdbc.Driver
    17. teacher:
    18. url: jdbc:mysql://shilei.tech:3306/tech?useSSL=true&serverTimezone=Asia/Shanghai
    19. username: root
    20. password: root123456
    21. driver-class-name: com.mysql.cj.jdbc.Driver
    22. druid:
    23. initial-size: 5
    24. min-idle: 1
    25. max-active: 20
    26. mybatis-plus:
    27. mapper-locations: classpath:/mapper/*.xml
    28. type-aliases-package: com.datasource.dynamicdatasource.model

    测试数据源切换

    数据源切换使用@DS注解,不使用此注解,使用默认数据源,方法上使用>类上使用 

    1. package com.datasource.dynamicdatasource.controller;
    2. import com.baomidou.dynamic.datasource.annotation.DS;
    3. import com.datasource.dynamicdatasource.model.Student;
    4. import com.datasource.dynamicdatasource.model.Teacher;
    5. import com.datasource.dynamicdatasource.service.StudentService;
    6. import com.datasource.dynamicdatasource.service.TeacherService;
    7. import org.springframework.beans.factory.annotation.Autowired;
    8. import org.springframework.web.bind.annotation.GetMapping;
    9. import org.springframework.web.bind.annotation.RestController;
    10. import java.util.List;
    11. /**
    12. * @author sl
    13. */
    14. @RestController
    15. public class TestDataSourceController {
    16. @Autowired
    17. private StudentService studentService;
    18. @Autowired
    19. private TeacherService teacherService;
    20. @GetMapping("/stu")
    21. public String getStu(){
    22. List allStudent = studentService.findAllStudent();
    23. return allStudent.toString();
    24. }
    25. @GetMapping("/tech")
    26. @DS("teacher")
    27. public String getTech(){
    28. List allTeacher = teacherService.findAllTeacher();
    29. return allTeacher.toString();
    30. }
    31. }

    项目启动日志中可以看到两个数据源的加载信息

     访问tech以及stu都能正常访问,代表动态数据源添加成功

    需要注意的问题

    使用多数据源要注意事务的控制,提交和回滚策略,可以观看spring多数据源事务解决方案

  • 相关阅读:
    “磐云杯”中职网络安全技能大赛A模块新题
    ls命令-使用频率最高的命令
    微信公众号h5写一个全局调用微信分享功能
    JSP SSH车间生产管理系统myeclipse开发mysql数据库MVC模式java编程网页设计
    【php快速上手(十一)】
    23届前端面试选择题(长期更新)
    MybatisPlus简单使用与自定义sql以及通过自定义sql实现多表联查的分页查询
    基于文本信息抽取的列控车载设备故障发现
    ViT:Vision transformer的cls token如何实现分类?
    2D物理引擎 Box2D for javascript Games 第六章 关节和马达
  • 原文地址:https://blog.csdn.net/m0_65775063/article/details/132873594