• Springboot+Atomikos+Jpa+Mysql实现JTA分布式事务


    1 前言

    之前整理了一个spring+jotm实现的分布式事务实现,但是听说spring3.X后不再支持jotm了,jotm也有好几年没更新了,所以今天整理springboot+Atomikos+jpa+mysql的JTA分布式事务实现。

    Atomikos网上的资料确实比jotm多,另外我发现STS工具里集成了Atomikos,那spring对Atomikos的支持毋庸置疑肯定会在相当长的时间内会是友好的。

    2 开发环境

    Springboot 1.0.1 + Atomikos 3.9.3 + JPA (Hibernate 4.3.5) + Mysql 5.1.73 + Mysql Connector 5.1.31 + Junit + Maven

    3 代码

    这套代码的基础我是从网上下载的,作了些修改,因为它原来用的是H2数据库,我改成了Mysql,另外,其实spring官网的例子也是这样写的,http://spring.io/blog/2011/08/15/configuring-spring-and-jta-without-full-java-ee/

    3.1 数据库sql

     1 DROP DATABASE IF EXISTS `datasource1`;
     2 CREATE DATABASE `datasource1` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; 
     3 
     4 use datasource1;
     5 
     6 DROP TABLE IF EXISTS `orders`;
     7 CREATE TABLE `orders` (
     8   `id` int(11) NOT NULL AUTO_INCREMENT,
     9   `code` int(11) DEFAULT NULL,
    10   `quantity` int(11) DEFAULT NULL,
    11   PRIMARY KEY (`id`)
    12 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
    13 
    14 DROP DATABASE IF EXISTS `datasource2`;
    15 CREATE DATABASE `datasource2` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; 
    16 
    17 use datasource2;
    18 
    19 DROP TABLE IF EXISTS `customer`;
    20 CREATE TABLE `customer` (
    21   `id` int(11) NOT NULL AUTO_INCREMENT,
    22   `name` varchar(45) DEFAULT NULL,
    23   `age` int(11) DEFAULT NULL,
    24   PRIMARY KEY (`id`)
    25 ) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

    3.2 部分重要代码

      pom.xml

     1 <dependencies>
     2         <dependency>
     3             <groupId>org.springframework.boot</groupId>
     4             <artifactId>spring-boot-starter-data-jpa</artifactId>
     5         </dependency>
     6         
     7         <!-- <dependency>
     8             <groupId>com.h2database</groupId>
     9             <artifactId>h2</artifactId>
    10         </dependency> -->
    11 
    12         <dependency>
    13             <groupId>org.projectlombok</groupId>
    14             <artifactId>lombok</artifactId>
    15             <version>1.12.4</version>
    16         </dependency>
    17 
    18         <dependency>
    19             <groupId>com.atomikos</groupId>
    20             <artifactId>transactions</artifactId>
    21             <version>3.9.3</version>
    22         </dependency>
    23 
    24         <dependency>
    25             <groupId>com.atomikos</groupId>
    26             <artifactId>transactions-jta</artifactId>
    27             <version>3.9.3</version>
    28         </dependency>
    29 
    30         <dependency>
    31             <groupId>com.atomikos</groupId>
    32             <artifactId>transactions-hibernate3</artifactId>
    33             <version>3.9.3</version>
    34             <exclusions>
    35                 <exclusion>
    36                     <artifactId>hibernate</artifactId>
    37                     <groupId>org.hibernate</groupId>
    38                 </exclusion>
    39             </exclusions>
    40         </dependency>
    41 
    42         <dependency>
    43             <groupId>org.springframework.boot</groupId>
    44             <artifactId>spring-boot-starter-test</artifactId>
    45         </dependency>
    46 
    47         <dependency>
    48             <groupId>junit</groupId>
    49             <artifactId>junit</artifactId>
    50             <scope>test</scope>
    51         </dependency>
    52         
    53         <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    54         <dependency>
    55             <groupId>mysql</groupId>
    56             <artifactId>mysql-connector-java</artifactId>
    57             <version>5.1.31</version>
    58         </dependency>
    59 
    60     </dependencies>

    application.properties

     1 spring.main.show_banner=false
     2 
     3 order.datasource.url=jdbc:mysql://192.168.0.12:3306/datasource1?serverTimezone=UTC
     4 order.datasource.user=root
     5 order.datasource.password=123456
     6 #jdbc:h2:order
     7 
     8 customer.datasource.url=jdbc:mysql://127.0.0.1:3312/datasource2?serverTimezone=UTC
     9 customer.datasource.user=root
    10 customer.datasource.password=123456
     1 import java.util.HashMap;
     2 
     3 import javax.sql.DataSource;
     4 
     5 import org.springframework.beans.factory.annotation.Autowired;
     6 import org.springframework.boot.context.properties.EnableConfigurationProperties;
     7 import org.springframework.context.annotation.Bean;
     8 import org.springframework.context.annotation.Configuration;
     9 import org.springframework.context.annotation.DependsOn;
    10 import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
    11 import org.springframework.orm.jpa.JpaVendorAdapter;
    12 import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
    13 
    14 import com.at.mul.repository.customer.CustomerDatasourceProperties;
    15 import com.atomikos.jdbc.AtomikosDataSourceBean;
    16 import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
    17 
    18 @Configuration
    19 @DependsOn("transactionManager")
    20 @EnableJpaRepositories(basePackages = "com.at.mul.repository.customer", entityManagerFactoryRef = "customerEntityManager", transactionManagerRef = "transactionManager")
    21 @EnableConfigurationProperties(CustomerDatasourceProperties.class)
    22 public class CustomerConfig {
    23 
    24     @Autowired
    25     private JpaVendorAdapter jpaVendorAdapter;
    26 
    27     @Autowired
    28     private CustomerDatasourceProperties customerDatasourceProperties;
    29 
    30     @Bean(name = "customerDataSource", initMethod = "init", destroyMethod = "close")
    31     public DataSource customerDataSource() {
    32         MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
    33         mysqlXaDataSource.setURL(customerDatasourceProperties.getUrl());
    34         mysqlXaDataSource.setUser(customerDatasourceProperties.getUser());
    35         mysqlXaDataSource.setPassword(customerDatasourceProperties.getPassword());
    36         mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
    37 
    38         AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
    39         xaDataSource.setXaDataSource(mysqlXaDataSource);
    40         xaDataSource.setUniqueResourceName("datasource2");
    41 //        xaDataSource.setXaDataSourceClassName("com.mysql.jdbc.jdbc2.optional.MysqlXADataSource");
    42         return xaDataSource;
    43     }
    44 
    45     @Bean(name = "customerEntityManager")
    46     @DependsOn("transactionManager")
    47     public LocalContainerEntityManagerFactoryBean customerEntityManager() throws Throwable {
    48 
    49         HashMap<String, Object> properties = new HashMap<String, Object>();
    50         properties.put("hibernate.transaction.jta.platform", AtomikosJtaPlatform.class.getName());
    51         properties.put("javax.persistence.transactionType", "JTA");
    52 
    53         LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
    54         entityManager.setJtaDataSource(customerDataSource());
    55         entityManager.setJpaVendorAdapter(jpaVendorAdapter);
    56         entityManager.setPackagesToScan("com.at.mul.domain.customer");
    57         entityManager.setPersistenceUnitName("customerPersistenceUnit");
    58         entityManager.setJpaPropertyMap(properties);
    59         return entityManager;
    60     }
    61 
    62 }
     1 @Configuration
     2 @DependsOn("transactionManager")
     3 @EnableJpaRepositories(basePackages = "com.at.mul.repository.order", entityManagerFactoryRef = "orderEntityManager", transactionManagerRef = "transactionManager")
     4 @EnableConfigurationProperties(OrderDatasourceProperties.class)
     5 public class OrderConfig {
     6 
     7     @Autowired
     8     private JpaVendorAdapter jpaVendorAdapter;
     9 
    10     @Autowired
    11     private OrderDatasourceProperties orderDatasourceProperties;
    12 
    13     @Bean(name = "orderDataSource", initMethod = "init", destroyMethod = "close")
    14     public DataSource orderDataSource() {
    15         MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
    16         mysqlXaDataSource.setURL(orderDatasourceProperties.getUrl());
    17         mysqlXaDataSource.setUser(orderDatasourceProperties.getUser());
    18         mysqlXaDataSource.setPassword(orderDatasourceProperties.getPassword());
    19 //        mysqlXaDataSource.setAllowMultiQueries(true);
    20 //        mysqlXaDataSource.setLogXaCommands(true);
    21         mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
    22 
    23         AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
    24         xaDataSource.setXaDataSource(mysqlXaDataSource);
    25         xaDataSource.setUniqueResourceName("datasource1");
    26 //        xaDataSource.setXaDataSourceClassName("com.mysql.jdbc.jdbc2.optional.MysqlXADataSource");
    27         return xaDataSource;
    28     }
    29 
    30     @Bean(name = "orderEntityManager")
    31     public LocalContainerEntityManagerFactoryBean orderEntityManager() throws Throwable {
    32 
    33         HashMap<String, Object> properties = new HashMap<String, Object>();
    34         properties.put("hibernate.transaction.jta.platform", AtomikosJtaPlatform.class.getName());
    35         properties.put("javax.persistence.transactionType", "JTA");
    36 
    37         LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
    38         entityManager.setJtaDataSource(orderDataSource());
    39         entityManager.setJpaVendorAdapter(jpaVendorAdapter);
    40         entityManager.setPackagesToScan("com.at.mul.domain.order");
    41         entityManager.setPersistenceUnitName("orderPersistenceUnit");
    42         entityManager.setJpaPropertyMap(properties);
    43         return entityManager;
    44     }
    45 
    46 }
     1 import com.at.mul.domain.customer.Customer;
     2 import com.at.mul.domain.order.Order;
     3 import com.at.mul.exception.NoRollbackException;
     4 import com.at.mul.exception.StoreException;
     5 
     6 public interface StoreService {
     7     
     8     void store(Customer customer, Order order) throws Exception;
     9     
    10     void storeWithStoreException(Customer customer, Order order) throws StoreException;
    11     
    12     void storeWithNoRollbackException(Customer customer, Order order) throws NoRollbackException;
    13 
    14 }
     1 import org.springframework.beans.factory.annotation.Autowired;
     2 import org.springframework.stereotype.Service;
     3 import org.springframework.transaction.annotation.Transactional;
     4 
     5 import com.at.mul.domain.customer.Customer;
     6 import com.at.mul.domain.order.Order;
     7 import com.at.mul.exception.NoRollbackException;
     8 import com.at.mul.exception.StoreException;
     9 import com.at.mul.repository.customer.CustomerRepository;
    10 import com.at.mul.repository.order.OrderRepository;
    11 
    12 @Service
    13 public class StoreServiceImpl implements StoreService {
    14     
    15     @Autowired
    16     private CustomerRepository customerRepository;
    17     
    18     @Autowired
    19     private OrderRepository orderRepository;
    20     
    21     @Transactional
    22     public void store(Customer customer, Order order) {
    23         customerRepository.save(customer);
    24         orderRepository.save(order);
    25     }
    26 
    27     @Transactional(rollbackFor = StoreException.class)
    28     public void storeWithStoreException(Customer customer, Order order) throws StoreException {
    29         customerRepository.save(customer);
    30         orderRepository.save(order);
    31         throw new StoreException();
    32     }
    33 
    34     @Transactional(noRollbackFor = NoRollbackException.class, rollbackFor = StoreException.class)
    35     public void storeWithNoRollbackException(Customer customer, Order order) throws NoRollbackException {
    36         customerRepository.save(customer);
    37         orderRepository.save(order);
    38         throw new NoRollbackException();
    39     }
    40 
    41 }

    完整代码下载:http://download.csdn.net/download/u013081610/9927514

    4 遇到的坑

    4.1 bug: The server time zone value '�й���׼ʱ��' is unrecognized or represents more than one time zone.

    http://blog.csdn.net/sunlggggg/article/details/54564114

    4.2 com.mysql.jdbc.jdbc2.optional.MysqlXAException: XAER_INVAL: Invalid arguments (or unsupported command)
    WARNING: XA resource 'jdbc/mysqlDs': resume for XID '3139322E3136382E31342E3131372E746D30303030323030303831:3139322E3136382E31342E3131372E746D32' raised -5: invalid arguments were given for the XA operation

    这个错误是我运行StoreServiceTest里的testStore()方法时出现的,就是把数据分别插入两个库的表里,之前用H2的时候都很正常,但是换成Mysql就是不行,操作第二个库的时候就报这个错,第一个不会报错。

    猜测可能是以下原因吧
    a.这可能是MySQL服务器对XA支持的限制,也就是可能是MySQL的一个bug,可以看Mysql官方文档的解释https://dev.mysql.com/doc/refman/5.5/en/xa-statements.html
    b.也可能是atomikos里的问题,具体看https://www.atomikos.com/Documentation/KnownProblems#ActiveMQ_error:_34Transaction_39XID:..._39_has_not_been_started_34
    找到下面的段落

    MySQL XA bug
    Some users have reported problems with MySQL XA (related to this MySQL bug: http://bugs.mysql.com/bug.php?id=27832external). This problem only happens if you access the same MySQL database more than once in the same transaction. A workaround can be setting the following property in jta.properties:
    com.atomikos.icatch.serial_jta_transactions=false
    Also, make sure to set the following property on the MySQL datasource:
    pinGlobalTxToPhysicalConnection="true"
    MariaDB's java driver also supports this workaround since v.1.1.8
    

    看来atomikos已经针对mysql的这个bug作了处理了,根据提示我加了mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true)就可以了。

    4.3 貌似对mysql的InnoDB引擎没用

    具体什么原因我还没研究,暂时测试只是使用的MyISAM

  • 相关阅读:
    flask连接数据库的URI书写格式
    touch事件中的touches、targetTouches和changedTouches
    postgresql自增字段初始值的设定
    ubuntu下使用apt-get install安装软件的安装位置
    微信小程序全局变量的设置、使用、修改
    微信小程序常用的3种提示弹窗
    vue练手项目——桌面时钟
    用原生JS实现爱奇艺首页导航栏
    vue-cli配置环境变量的方法
    cross-env解读
  • 原文地址:https://www.cnblogs.com/shamo89/p/7326718.html
Copyright © 2020-2023  润新知