在现代的应用程序中,使用多个数据源来处理不同的业务需求已成为常态。然而,处理多数据源之间的分布式事务是一个复杂的问题。本文将介绍如何使用Spring Boot、Druid和dynamic-datasource-spring-boot-starter来实现多数据源的分布式事务。
在传统的单数据源应用中,事务管理相对简单。但当引入多个数据源时,事务管理变得更加复杂。为了解决这个问题,我们将使用Spring Boot作为应用程序的基础框架,Druid作为数据源连接池,并结合dynamic-datasource-spring-boot-starter来实现动态数据源切换和分布式事务管理。
Druid是一款高性能的数据库连接池,具有强大的监控和统计功能。在Spring Boot项目中集成Druid非常简单,只需添加相关依赖并进行配置即可。可以通过以下步骤来完成集成:
com.alibaba druid-spring-boot-starter 1.2.16 com.baomidou dynamic-datasource-spring-boot-starter 3.5.2
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
@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; } }
给方法加上@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; } } }
@Configuration public class DruidConfig { /** * 配置Druid 监控启动页面 * * @return servletRegistrationBean */ @Bean @ConditionalOnMissingBean public ServletRegistrationBeandruidStartViewServlet() { 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; } }
在使用多数据源进行分布式事务时,需要注意以下几点:
本文介绍了如何使用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