专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

Spring-Boot 多数据源配置+动态数据源切换+多数据源事物配置实现主从数据库存储分离

一、基础介绍

  多数据源字面意思,比如说二个数据库,甚至不同类型的数据库。在用SpringBoot开发项目时,随着业务量的扩大,我们通常会进行数据库拆分或是引入其他数据库,从而我们需要配置多个数据源。

二、项目目录截图

77_1.png

三、多数据源SQL结构设计如下(简单的主从关系):

77_2.png

PS:创建两个库用于搭建项目中主从使用不同的数据库,表可以随意定义。

四、配置编码

1、数据源自定义注解,DataSource.java



@Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface DataSource {      DataSourcesType name() default DataSourcesType.MASTER; }

2、数据源类型枚举类定义,DataSourcesType.java


public enum DataSourcesType { MASTER, SLAVE }

3、多数据源application.yml配置文件配置

# 数据源配置
spring:
    datasource:
      type: com.alibaba.druid.pool.DruidDataSource
      driverClassName: com.mysql.cj.jdbc.Driver
      druid:
          master:
              url: jdbc:mysql://127.0.0.1:3306/master?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
              username: root
              password: 123456
          slave:
              enable: true
              url: jdbc:mysql://127.0.0.1:3306/slave?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
              username: root
              password: 123456
          # 初始连接数
          initialSize: 5
          # 最小连接池数量
          minIdle: 10
          # 最大连接池数量
          maxActive: 20
          # 配置获取连接等待超时的时间
          maxWait: 60000
          # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
          timeBetweenEvictionRunsMillis: 60000
          # 配置一个连接在池中最小生存的时间,单位是毫秒
          minEvictableIdleTimeMillis: 300000
          # 配置一个连接在池中最大生存的时间,单位是毫秒
          maxEvictableIdleTimeMillis: 900000
          validationQuery: SELECT 1 FROM DUAL
          testWhileIdle: true
          testOnBorrow: false
          testOnReturn: false
          # 打开PSCache,并且指定每个连接上PSCache的大小
          poolPreparedStatements: true
          maxPoolPreparedStatementPerConnectionSize: 20
          # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙,此处是filter修改的地方
          filters:
            commons-log.connection-logger-name: stat,wall,log4j
          # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
          connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
          # 合并多个DruidDataSource的监控数据
          useGlobalDataSourceStat: true
          # 配置 DruidStatFilter
          web-stat-filter:
            enabled: true
            url-pattern: 

4、数据源配置文件属性定义,DataSourceProperties.java


@Setter @Configuration @ConfigurationProperties(prefix = "spring.datasource.druid") public class DataSourceProperties { private int initialSize; private int minIdle; private int maxActive; private int maxWait; private int timeBetweenEvictionRunsMillis; private int minEvictableIdleTimeMillis; private int maxEvictableIdleTimeMillis; private String validationQuery; private boolean testWhileIdle; private boolean testOnBorrow; private boolean testOnReturn; public DruidDataSource setDataSource(DruidDataSource datasource) { datasource.setInitialSize(initialSize); datasource.setInitialSize(initialSize); datasource.setMaxActive(maxActive); datasource.setMinIdle(minIdle); datasource.setMaxWait(maxWait); datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis); datasource.setValidationQuery(validationQuery); datasource.setTestWhileIdle(testWhileIdle); datasource.setTestOnBorrow(testOnBorrow); datasource.setTestOnReturn(testOnReturn); return datasource; }

5、多数据源切换处理,DynamicDataSourceContextHolder.java


public class DynamicDataSourceContextHolder { public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); public static void setDataSourceType(String dataSourceType) { log.info("已切换到{}数据源", dataSourceType); contextHolder.set(dataSourceType); } public static String getDataSourceType() { return contextHolder.get(); } public static void removeDataSourceType() { contextHolder.remove(); } }

6、获取数据源(依赖于 spring) 定义一个类继承AbstractRoutingDataSource实现determineCurrentLookupKey方法,该方法可以实现数据库的动态切换,DynamicDataSource.java


public class DynamicDataSource extends AbstractRoutingDataSource { public static DynamicDataSource build() { return new DynamicDataSource(); } @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType(); } }

7、数据源核心配置类,DataSourceConfiguration.java


@Configuration public class DataSourceConfiguration { @Bean @ConfigurationProperties("spring.datasource.druid.master") public DataSource masterDataSource(DataSourceProperties dataSourceProperties) { return dataSourceProperties.setDataSource(DruidDataSourceBuilder.create().build()); } @Bean @ConditionalOnProperty( prefix = "spring.datasource.druid.slave", name = "enable", havingValue = "true")//是否开启数据源开关 @ConfigurationProperties("spring.datasource.druid.slave") public DataSource slaveDataSource(DataSourceProperties dataSourceProperties) { return dataSourceProperties.setDataSource(DruidDataSourceBuilder.create().build()); } @Bean(name = "dynamicDataSource") @Primary public DynamicDataSource dynamicDataSource(DataSource masterDataSource, DataSource slaveDataSource) { Map<Object, Object> targetDataSources = new HashMap<>(); DynamicDataSource dynamicDataSource = DynamicDataSource.build(); targetDataSources.put(DataSourcesType.MASTER.name(), masterDataSource); targetDataSources.put(DataSourcesType.SLAVE.name(), slaveDataSource); //默认数据源配置 DefaultTargetDataSource dynamicDataSource.setDefaultTargetDataSource(masterDataSource); //额外数据源配置 TargetDataSources dynamicDataSource.setTargetDataSources(targetDataSources); dynamicDataSource.afterPropertiesSet(); return dynamicDataSource; } }

8、多数据源切面配置类,用于获取注解上的注解,进行动态切换数据源,DynamicDataSourceAspect.java

@Aspect
@Component
@Order(-1) // 保证该AOP在@Transactional之前执行
public class DynamicDataSourceAspect {

    protected Logger logger = LoggerFactory.getLogger(getClass());

    @Pointcut("@annotation(com.fuzongle.tankboot.common.annotation.DataSource)"
            + "|| @within(com.fuzongle.tankboot.common.annotation.DataSource)")
    public void dsPointCut()  {
    }

    @Around("dsPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        Method targetMethod = this.getTargetMethod(point);
        DataSource dataSource = targetMethod.getAnnotation(DataSource.class);//获取要切换的数据源
        if (dataSource != null)  {
            DynamicDataSourceContextHolder.setDataSourceType(dataSource.name().name());
        }
        try {
            return point.proceed();
        }
        finally  {
            // 销毁数据源 在执行方法之后
            DynamicDataSourceContextHolder.removeDataSourceType();
        }
    }

    private Method getTargetMethod(ProceedingJoinPoint pjp) throws NoSuchMethodException {
        Signature signature = pjp.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method agentMethod = methodSignature.getMethod();
        return pjp.getTarget().getClass().getMethod(agentMethod.getName(), agentMethod.getParameterTypes());
    }
}
9.编写业务逻辑,切换从库查询数据。

77_3.png

10、编写测试方法,调用查询业务,查看是否切换数据源是否生效。

77_4.png

PS:这种多数据源的动态切换确实可以解决数据的主从分库操作,但是却有一个致命的BUG,那就是事务不但失效而且无法实现

一致性,因为涉及到跨库,因此我们必须另想办法来实现事务的ACID原则

        以上配置所有源码地址:https://gitee.com/fuzongle/java-bucket

注意:

1、如果有任何不懂的地方可以关注公众号就可以加我微信,随时欢迎互相帮助。

2、技术交流群QQ:422167709。

3、如果希望学习更多,希望微信扫码,长按扫码,帮忙关注一下,举手之劳,当您无助的时候真的能帮你。非常感谢您关注公众号 “编程小乐”。

77_5.png

文章永久链接:https://tech.souyunku.com/21829

未经允许不得转载:搜云库技术团队 » Spring-Boot 多数据源配置+动态数据源切换+多数据源事物配置实现主从数据库存储分离

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们