相关推荐recommended
SpringBoot+Durid+dynamic-datasource实现多数据源事务
作者:mmseoamin日期:2024-02-04

SpringBoot+Durid+dynamic实现多数据源分布式事务

SpringBoot+Durid+dynamic-datasource实现多数据源事务,在这里插入图片描述,第1张

引言:

在现代的应用程序中,使用多个数据源来处理不同的业务需求已成为常态。然而,处理多数据源之间的分布式事务是一个复杂的问题。本文将介绍如何使用Spring Boot、Druid和dynamic-datasource-spring-boot-starter来实现多数据源的分布式事务。

背景

在传统的单数据源应用中,事务管理相对简单。但当引入多个数据源时,事务管理变得更加复杂。为了解决这个问题,我们将使用Spring Boot作为应用程序的基础框架,Druid作为数据源连接池,并结合dynamic-datasource-spring-boot-starter来实现动态数据源切换和分布式事务管理。

集成Druid数据源连接池和dynamic动态数据源切换和分布式事务管理

Druid是一款高性能的数据库连接池,具有强大的监控和统计功能。在Spring Boot项目中集成Druid非常简单,只需添加相关依赖并进行配置即可。可以通过以下步骤来完成集成:

  1. 在pom.xml文件中添加依赖。

    com.alibaba
    druid-spring-boot-starter
    1.2.16


    com.baomidou
    dynamic-datasource-spring-boot-starter
    3.5.2

  1. 在application.properties或application.yml文件中配置Druid和dynamic的相关参数。
spring:
  datasource:
    dynamic:
      # druid连接池设置
      druid:
        # 配置初始化线程数
        initialSize: 5
        # 最小线程数
        minIdle: 5
        # CPU核数+1,也可以大些但不要超过20,数据库加锁时连接过多性能下降
        maxActive: 11
        # 最大等待时间,内网:800,外网:1200(三次握手1s)
        maxWait: 60000
        # 连接可空闲存活时间(ms)
        timeBetweenEvictionRunsMillis: 60000
        # 连接保持空闲而不被驱逐的最长存活时间(ms)
        minEvictableIdleTimeMillis: 300000
        # 用来检测连接是否有效的sql,如果validationQuery为空,那么testOnBorrow、testOnReturn、testWhileIdle这三个参数都不会起作用
        validationQuery: SELECT 1
        # 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效;
        testWhileIdle: true
        # 建议配置为false,申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
        testOnBorrow: false
        # 建议配置为false,归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能;
        testOnReturn: false
        # PSCache对支持游标的数据库性能提升巨大
        poolPreparedStatements: true
        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
        filters: stat
        # 保持minIdle数量的长连接
        keepAlive: true
        # 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。
        # 在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100。缺省值为-1
        maxPoolPreparedStatementPerConnectionSize: 20
        # 是否合并多个DruidDataSource的监控数据
        useGlobalDataSourceStat: true
        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
      primary: huawei
      datasource:
        # 主库数据源
        master:
          driverClassName: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://10.211.55.6:13306/ddz?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true
          username: root
          password: root
        # 从库数据源
        slave:
          driverClassName: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://10.211.55.5:13306/ddz?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true
          username: root
          password: root
# MyBatis Plus配置
mybatis-plus:
  # 搜索指定包别名
  typeAliasesPackage: com.ddz.**.domain
  # 配置mapper的扫描,找到所有的mapper.xml映射文件
  mapperLocations: classpath*:mapper/**/*Mapper.xml
  configuration:
    # 使全局的映射器启用或禁用缓存
    cache-enabled: true
    # 允许JDBC 支持自动生成主键
    use-generated-keys: true
    # 配置默认的执行器.SIMPLE就是普通执行器;REUSE执行器会重用预处理语句(prepared statements);BATCH执行器将重用语句并执行批量更新
    default-executor-type: simple
    # 指定 MyBatis 所用日志的具体实现
    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
    # 使用驼峰命名法转换字段
    map-underscore-to-camel-case: true
  1. 创建两个Mapper类和实体类
@DS("master")
public interface UserMapper extends BaseMapper {
    /**
     * 新增数据
     *
     * @param user 实例对象
     * @return 影响行数
     */
    int insertSql(User user);
}
@DS("slave")
public interface ScoreMapper extends BaseMapper {
    /**
     * 新增数据
     *
     * @param score 实例对象
     * @return 影响行数
     */
    int insertSql(Score score);
}
@Data
public class User {
    private Integer id;
    private String name;
    private Integer balance;
    private String remark;
    public User(String name, Integer balance) {
        this.name = name;
        this.balance = balance;
    }
}
@Data
public class Score {
    private Integer id;
    private Integer count;
    public Score(Integer count) {
        this.count = count;
    }
}
  1. 编写Controller测试

    给方法加上@DSTransactional注解实现事务管理

@RestController
@RequestMapping("user")
public class UserController {
    @Resource
    private UserMapper userMapper;
    @Resource
    private ScoreMapper scoreMapper;
    @GetMapping("/get")
    @DSTransactional
    public void get() {
    	// 随机触发异常检查两个数据源的一致性
        int i = random.nextInt(10);
        userMapper.insertSql(new User("ddz", i));
        scoreMapper.insertSql(new Score(i*10));
        Random random = new Random();
        if (i % 2 == 0) {
            i = 1 / 0;
        }
    }
}
  1. 需要durid监控页面的添加配置类
@Configuration
public class DruidConfig {
    /**
     * 配置Druid 监控启动页面
     *
     * @return servletRegistrationBean
     */
    @Bean
    @ConditionalOnMissingBean
    public ServletRegistrationBean druidStartViewServlet() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        // 白名单
        servletRegistrationBean.addInitParameter("allow", "127.0.0.1");
        // 黑名单
//        servletRegistrationBean.addInitParameter("deny", "");
        // 登录查看信息的账密,用于登录Druid监控后台
        servletRegistrationBean.addInitParameter("loginUsername", "ddz");
        servletRegistrationBean.addInitParameter("loginPassword", "ddz2023");
        // 是否能够重置数据
        servletRegistrationBean.addInitParameter("resetEnable", "true");
        return servletRegistrationBean;
    }
 
    /**
     * Druid监控过滤器配置规则
     * ConditionalOnMissingBean 防止注册相同的bean
     *
     * @return filterFilterRegistrationBean
     */
    @Bean
    @ConditionalOnMissingBean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterFilterRegistrationBean = new FilterRegistrationBean<>();
        filterFilterRegistrationBean.setFilter(new WebStatFilter());
        // 添加过滤规则
        filterFilterRegistrationBean.addUrlPatterns("/*");
        // 添加不需要忽略的格式信息
        filterFilterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterFilterRegistrationBean;
    }
}
  1. 添加好配置类后重启项目在页面输入http://127.0.0.1:8080/druid/login.html访问

    SpringBoot+Durid+dynamic-datasource实现多数据源事务,在这里插入图片描述,第2张

分布式事务的注意事项

在使用多数据源进行分布式事务时,需要注意以下几点:

  • 数据源配置和事务管理的正确性,包括数据源的连接信息、事务的传播行为等。
  • 事务的超时和回滚策略,确保事务在适当的时间内完成或回滚。
  • 对于不同数据源之间的数据一致性要求高的场景,可以使用分布式事务管理器(如Atomikos、Bitronix等)来实现XA事务。
  • 针对不同的数据库类型,可能需要配置特定的事务管理器和驱动程序。

    总结

    本文介绍了如何使用Spring Boot、Druid和dynamic-datasource-spring-boot-starter来实现多数据源的分布式事务。通过合理配置数据源连接池、动态数据源和事务管理,我们可以有效地处理多数据源下的事务一致性问题。在实际应用中,根据具体场景和需求,可以选择适合的分布式事务管理方案。

    参考资料:

    dynamic-datasource-spring-boot-starter文档:https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611

    Spring事务管理文档:https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#transaction

    Druid官方文档:https://github.com/alibaba/druid