springboot mybatis 事务_springboot+mybatis动态数据源JTA解决事务问题

在单体应用中,事务的使用已经非常方便了。spring提供注解方式直接实现事务。然而在多数据源动态切换场景增加事务则出现数据源无法切换。因为我们在service开启一个事务,则会缓DataSource、SqlSessionFactory、Connection等。再去切换数据源时则直接从缓存取链接,造成数据源无法正常切换的问题。有兴趣的可以查看DataSourceTransactionManager类的源码。

下面则介绍使用JTA(Java Transactional Api)解决这个问题

1.JTA介绍

XA与JTA

XA规范定义了:
1.1.1. TransactionManager : 这个TransactionManager可以通过管理多个ResourceManager来管理多 个Resouce,也就是管理多个数据源
1.1.2. XAResource : 针对数据资源封装的一个接口
1.1.3. 两段式提交 : 多数据源事务提交的机制

JTA : 是Java规范,是XA在Java上的实现
1.2.1. TransactionManager : 常用方法,可以开启,回滚,获取事务. begin(),rollback()...
1.2.2. XAResouce : 资源管理,通过Session来进行事务管理,commit(xid)...
1.2.3. XID : 每一个事务都分配一个特定的XID

  1. JTA的作用

JTA的作用是同时管理多个数据源的事务管理,满足多数据源情况下的数据一致性。

3. JTA的实现

3.1 使用Atomikos事务管理器 pom文件中引入starter

org.springframework.boot

spring-boot-starter-jta-atomikos

3.2 项目代码改造(动态多数据源部分省略)

在原有多数据源基础上需要修改的部分,DataSource 更改为 MysqlXADataSource,也可以使用DruidXADataSource。自行选择

重写Transaction类,代码如下:

import com.***.common.jdbc.multi.DynamicDataSourceContextHolder;import lombok.extern.slf4j.Slf4j;import org.apache.ibatis.transaction.Transaction;import org.springframework.jdbc.CannotGetJdbcConnectionException;import org.springframework.jdbc.datasource.DataSourceUtils;import javax.sql.DataSource;import java.sql.Connection;import java.sql.SQLException;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.ConcurrentMap;import static org.apache.commons.lang3.Validate.notNull;/** * 多数据源切换,支持事务 */@Slf4jpublic class MultiDataSourceTransaction implements Transaction { private final DataSource dataSource; private Connection connection; private String databaseIdentification; private ConcurrentMap connectionMap; private boolean isConnectionTransactional; private boolean autoCommit; public MultiDataSourceTransaction(DataSource dataSource) { notNull(dataSource, "No DataSource specified"); this.dataSource = dataSource; connectionMap = new ConcurrentHashMap<>(); databaseIdentification = DynamicDataSource.getDataSource(); } /** * 获取数据连接 */ @Override public Connection getConnection() throws SQLException { String databaseIdentification = DynamicDataSource.getDataSource(); if (databaseIdentification.equals(databaseIdentification)) { if (connection != null) return connection; else { openConnection(); databaseIdentification = databaseIdentification; return connection; } } else { if (!connectionMap.containsKey(databaseIdentification)) { try { Connection conn = dataSource.getConnection(); connectionMap.put(databaseIdentification, conn); } catch (SQLException ex) { throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex); } } return connectionMap.get(databaseIdentification); } } /** * 打开事务 */ private void openConnection() throws SQLException { this.connection = DataSourceUtils.getConnection(this.dataSource); this.autoCommit = this.connection.getAutoCommit(); this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource); if (log.isDebugEnabled()) { log.debug( "JDBC Connection [" + this.connection + "] will" + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring"); } } /** * 提交 */ @Override public void commit() throws SQLException { if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) { if (log.isDebugEnabled()) { log.debug("Committing JDBC Connection [" + this.connection + "]"); } this.connection.commit(); for (Connection connection : connectionMap.values()) { connection.commit(); } } } /** * 回滚 */ @Override public void rollback() throws SQLException { if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) { if (log.isDebugEnabled()) { log.debug("Rolling back JDBC Connection [" + this.connection + "]"); } this.connection.rollback(); for (Connection connection : connectionMap.values()) { connection.rollback(); } } } /** * 关闭 */ @Override public void close() throws SQLException { DataSourceUtils.releaseConnection(this.connection, this.dataSource); for (Connection connection : connectionMap.values()) { DataSourceUtils.releaseConnection(connection, this.dataSource); } } @Override public Integer getTimeout() throws SQLException { return null; }}

重写SpringManagedTransactionFactory

import org.apache.ibatis.session.TransactionIsolationLevel;import org.apache.ibatis.transaction.Transaction;import org.mybatis.spring.transaction.SpringManagedTransactionFactory;import javax.sql.DataSource;/** * 支持多数据源切换的Factory */public class MultiDataSourceTransactionFactory extends SpringManagedTransactionFactory { @Override public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) { return new MultiDataSourceTransaction(dataSource); }}

mybatis配置引入此Factory

@Bean@Qualifier("sqlSessionFactory")public SqlSessionFactoryBean sqlSessionFactory(@Autowired DataSource dataSource) throws IOException {SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dataSource); // 引入factory sqlSessionFactoryBean.setTransactionFactory(new MultiDataSourceTransactionFactory());org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();configuration.setMapUnderscoreToCamelCase(true);configuration.addInterceptor(new PageInterceptor());sqlSessionFactoryBean.setConfiguration(configuration);ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath*:mapper/*.xml"));return sqlSessionFactoryBean;}

最后正常使用@Transactional注解实现事务。

PS: JTA使用两段式提交事务方式,事务长度增加,则相对性能,吞吐量会造成一定的影响。

本文主要使用XA分布式事务解决多数据源事务问题,后面继续更新微服务下的分布式事务解决方案及使用过程中踩的坑,先踩坑去了~~~~,项目正常撸码中,更新会比较慢。

f4a9bb6fb23902f121a6eac85fd38828.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot 是一个用于快速构建 Java 应用程序的框架。它可以与多种其他框架和组件进行整合,以实现更丰富的功能。在这里,我们将讨论如何使用 Spring Boot 整合 Druid、MyBatisJTA 分布式事务以及多数据源,同时使用 AOP 注解实现动态切换。 首先,我们可以在 Spring Boot 中集成 Druid 数据源。Druid 是一个高性能的 JDBC 连接池,可以提供监控和统计功能。我们可以通过在 pom.xml 文件中添加相关的依赖,并在 application.properties 文件中配置数据源信息,来实现 Druid 的集成。 接下来,我们可以整合 MyBatis 框架,它是一种优秀的持久化解决方案。我们可以使用 MyBatis 来操作数据库,并将其与 Druid 数据源进行整合。为此,我们需要在 pom.xml 文件中添加 MyBatisMyBatis-Spring 的依赖,并配置 MyBatis 的相关配置文件。 此外,我们还可以使用 JTA(Java Transaction API)实现分布式事务JTA 可以在分布式环境中协调多个参与者的事务操作。我们可以在 pom.xml 文件中添加 JTA 的依赖,并在 Spring Boot 的配置文件中配置 JTA 的相关属性,以实现分布式事务的支持。 在实现多数据源时,我们可以使用 Spring Boot 的 AbstractRoutingDataSource 来实现动态切换数据源。这个类可以根据当前线程或其他条件选择不同的数据源来进行数据操作。我们可以通过继承 AbstractRoutingDataSource 并实现 determineCurrentLookupKey() 方法来指定当前数据源的 key。然后,在配置文件中配置多个数据源,并将数据源注入到 AbstractRoutingDataSource 中,从而实现动态切换。 最后,我们可以使用 AOP(Aspect Oriented Programming)注解来实现动态切换。AOP 是一种编程范式,可以通过在代码中插入特定的切面(Aspect)来实现横切关注点的处理。我们可以在代码中使用注解来标记需要切换数据源的方法,然后使用 AOP 技术来拦截这些方法,并根据注解中指定的数据源信息来进行数据源的切换。 综上所述,通过整合 Druid、MyBatisJTA 分布式事务以及多数据源,并使用 AOP 注解实现动态切换,我们可以在 Spring Boot 中实现强大而灵活的应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值