赞
踩
今天偶然在网上看到可以使用jta来实现分布式事务,决定动手尝试一下。
在网上查阅了各种资料,也遇到了很多问题,在这里分享一下。
我这里是两个数据库下的两张一样的表Example。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.7.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.7.3</version> </dependency> <!-- mybatis --> <!-- mysql连接依赖包--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <!-- 分布式事务--> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-jta-atomikos --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jta-atomikos</artifactId> <version>2.7.3</version> </dependency>
................... # Spring spring: # mybstis datasource: test1: driver-class-name: com.mysql.cj.jdbc.Driver # &allowMultiQueries=true&rewriteBatchedStatements =true url: jdbc:mysql://localhost:3306/springboot_base?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&rewriteBatchedStatements=true username: root password: root test2: driver-class-name: com.mysql.cj.jdbc.Driver # &allowMultiQueries=true&rewriteBatchedStatements =true url: jdbc:mysql://localhost:3306/springboottest?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&rewriteBatchedStatements=true username: root password: root .............
DataSource1Config.java文件
package com.yss.www.DTP; import com.mysql.cj.jdbc.MysqlXADataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import javax.sql.DataSource; import java.sql.SQLException; /** * @author yss * @date 2023/3/17 */ @Configuration @MapperScan(basePackages = "com.yss.www.dev.mapper.test1", sqlSessionTemplateRef = "test1SqlSessionTemplate") public class DataSource1Config { @Bean(name = "test1DataSource") @ConfigurationProperties(prefix = "spring.datasource.test1") @Primary public DataSource testDataSource() throws SQLException { MysqlXADataSource mysqlXADataSource = new MysqlXADataSource(); mysqlXADataSource.setURL("jdbc:mysql://localhost:3306/springboot_base?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&rewriteBatchedStatements=true"); mysqlXADataSource.setPinGlobalTxToPhysicalConnection(true); mysqlXADataSource.setPassword("root"); mysqlXADataSource.setUser("root"); mysqlXADataSource.setPinGlobalTxToPhysicalConnection(true); //创建atomikos全局事务 AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean(); xaDataSource.setXaDataSource(mysqlXADataSource); xaDataSource.setUniqueResourceName("test1DataSource"); return xaDataSource; } @Bean(name = "test1SqlSessionFactory") @Primary public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); return bean.getObject(); } @Bean(name = "test1SqlSessionTemplate") @Primary public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } }
DataSource2Config.java文件
package com.yss.www.DTP; import com.mysql.cj.jdbc.MysqlXADataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; import java.sql.SQLException; /** * @author yss * @date 2023/3/17 */ @Configuration @MapperScan(basePackages = "com.yss.www.dev.mapper.test2", sqlSessionTemplateRef = "test2SqlSessionTemplate") public class DataSource2Config { @Bean(name = "test2DataSource") @ConfigurationProperties(prefix = "spring.datasource.test2") public DataSource testDataSource() throws SQLException { MysqlXADataSource mysqlXADataSource = new MysqlXADataSource(); mysqlXADataSource.setURL("jdbc:mysql://localhost:3306/springboottest?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&rewriteBatchedStatements=true"); mysqlXADataSource.setPinGlobalTxToPhysicalConnection(true); mysqlXADataSource.setPassword("root"); mysqlXADataSource.setUser("root"); mysqlXADataSource.setPinGlobalTxToPhysicalConnection(true); //创建atomikos全局事务 AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean(); xaDataSource.setXaDataSource(mysqlXADataSource); xaDataSource.setUniqueResourceName("test2DataSource"); return xaDataSource; } @Bean(name = "test2SqlSessionFactory") public SqlSessionFactory testSqlSessionFactory(@Qualifier("test2DataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); return bean.getObject(); } @Bean(name = "test2SqlSessionTemplate") public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } }
Example1Mapper接口
package com.yss.www.dev.mapper.test1; import com.yss.www.dev.domain.Example; import org.apache.ibatis.annotations.*; @Mapper public interface Example1Mapper { @Insert("INSERT INTO example(name,status) VALUES(#{name}, #{status})") Integer insert(Example example); @Update("UPDATE example SET name=#{name},status=#{status} WHERE id =#{id}") Integer update(Example example); @Delete("DELETE FROM example WHERE id =#{id}") Integer delete(Long id); }
Example2Mapper接口
package com.yss.www.dev.mapper.test2; import com.yss.www.dev.domain.Example; import org.apache.ibatis.annotations.*; @Mapper public interface Example2Mapper { @Insert("INSERT INTO example(name,status) VALUES(#{name}, #{status})") Integer insert(Example example); @Update("UPDATE example SET name=#{name},status=#{status} WHERE id =#{id}") Integer update(Example example); @Delete("DELETE FROM example WHERE id =#{id}") Integer delete(Long id); }
因为篇幅原因这里就不粘贴service接口了。
Example1ServoceImpl
package com.yss.www.dev.server.impl; import com.yss.www.dev.domain.Example; import com.yss.www.dev.mapper.test1.Example1Mapper; import com.yss.www.dev.mapper.test2.Example2Mapper; import com.yss.www.dev.server.Example1Servoce; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * @author yss * @date 2023/3/17 */ @Service @RequiredArgsConstructor public class Example1ServoceImpl implements Example1Servoce { private final Example1Mapper example1Mapper; private final Example2Mapper example2Mapper; @Override @Transactional public Integer insert(Example example) { Integer integer = 0; // 调用test1库的插入方法 Integer t1 = example1Mapper.insert(example); // 调用test2库的插入方法 Integer t2 = example2Mapper.insert(example); integer = t1 + t2; return integer; } @Override public Integer update(Example example) { return example1Mapper.update(example); } @Override public Integer delete(Long id) { return null; } }
测试类
@Resource
public Example1Servoce example1Servoce;
@Test
public void testGTP(){
Example example = new Example();
example.setName("EEEE");
example.setStatus("1");
log.info(""+example1Servoce.insert(example));
}
参考: 大神博客
问题一: Mysql报错Fatal error occurred in the transaction branch - check your data for consistency
执行命令: GRANT XA_RECOVER_ADMIN ON *.* TO root@'%';
参考: 大神博客
问题二: @Transactional 失效了 无法增删改
spring事务失效场景
- 要在拥有@Service注解的情况下,事务才生效
- @Transactional 修饰的一定是public方法,否则失效
- 使用IOC容器管理的bean才能生效,直接new出来的对象不生效
- 数据库存储引擎不支持事务,也会失效
- 当前类内部调用两个方法,a方法没有@Transactional 注解 b方法有,a方法中调用b方法,事务失效。
- try catch 中发生异常,异常被吃了,事务失效
- 异常类型错误 默认是 RuntimeException异常。如果你想触发其他异常的回滚,需要在注解上配置一下,如:
@Transactional(rollbackFor = Exception.class)
参考 大神博客
问题三: spring事务传播机制
@Transactional(propagation= Propagation.REQUIRED)
public void methodA(){
methodB();
// do something
}
@Transactional(propagation= Propagation.REQUIRED)
public void methodB(){
// do something
}
调用methdoA,如果methodB发生异常,触发事务回滚,也会methodA中的也会回滚。
2.SUPPORTS
PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
@Transactional(propagation= Propagation.REQUIRED)
public void methodA(){
methodB();
// do something
}
@Transactional(propagation= Propagation.SUPPORTS)
public void methodB(){
// do something
}
如果调用methodA,再调用methodB,MehtodB会加入到MethodA的开启的当前事务中。如果直接调用methodB,当前没有事务,就以非事务执行。
参考 大神博客
问题四: 如果两个serviceImpl同时实现了一个接口类会怎么样?如何处理?
如果没有指定service的名称,则启动会报错;指定service名称后,使用时也需要去指定使用那个service,不然也会报错。
参考 大神博客
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。