atomikos JTA/XA全局事务

Atomikos公司官方网址为:https://www.atomikos.com/。其旗下最著名的产品就是事务管理器。产品分两个版本:

TransactionEssentials:开源的免费产品

ExtremeTransactions:上商业版,需要收费。

这两个产品的关系如下图所示: 

atomikos JTA/XA全局事务_第1张图片

TransactionEssentials:

1、实现了JTA/XA规范中的事务管理器(Transaction Manager)应该实现的相关接口,如:

    UserTransaction实现是com.atomikos.icatch.jta.UserTransactionImp,用户只需要直接操作这个类

    TransactionManager实现是com.atomikos.icatch.jta.UserTransactionManager

    Transaction实现是com.atomikos.icatch.jta.TransactionImp

2、针对实现了JDBC规范中规定的实现了XADataSource接口的数据库连接池,以及实现了JMS规范的MQ客户端提供一层封装。

     在上一节我们讲解JTA规范时,提到过XADataSource、XAConnection等接口应该由资源管理器RM来实现,而Atomikos的作用是一个事务管理器(TM),并不需要提供对应的实现。而Atomikos对XADataSource进行封装,只是为了方便与事务管理器整合。封装XADataSource的实现类为AtomikosDataSourceBean。典型的XADataSource实现包括:

    1、mysql官方提供的com.mysql.jdbc.jdbc2.optional.MysqlXADataSource

    2、阿里巴巴开源的druid连接池,对应的实现类为com.alibaba.druid.pool.xa.DruidXADataSource

    3、tomcat-jdbc连接池提供的org.apache.tomcat.jdbc.pool.XADataSource

    而其他一些常用的数据库连接池,如dbcp、dbcp2或者c3p0,目前貌似尚未提供XADataSource接口的实现。如果提供给AtomikosDataSourceBean一个没有实现XADataSource接口的数据源,如c3p0的ComboPooledDataSource,则会抛出类似以下异常: 

 
  1. com.atomikos.jdbc.AtomikosSQLException: The class 'com.mchange.v2.c3p0.ComboPooledDataSource'
  2.  specified by property 'xaDataSourceClassName' does not implement the required interface  
  3.  javax.jdbc.XADataSource.  
  4.  Please make sure the spelling is correct, and check your JDBC driver vendor's documentation.

   

ExtremeTransactions在TransactionEssentials的基础上额外提供了以下功能:

支持TCC:这是一种柔性事务

支持通过RMI、IIOP、SOAP这些远程过程调用技术,进行事务传播。

本文主要针对Atomikos开源版本的事务管理器实现TransactionEssentials进行讲解,包括:

1、直接使用TransactionEssentials的API

2、TransactionEssentials与spring、mybatis整合

3、Atomikos配置详解

 

直接使用TransactionEssentials的API

在maven项目的pom文件中引入以下依赖:

 
  1.     com.atomikos
  2.     transactions-jdbc
  3.     4.0.6
  4.     mysql
  5.     mysql-connector-java
  6.     5.1.39

新建mysql数据库表

需要注意的是,在mysql中,只有innodb引擎才支持XA事务,所以这里显式的指定了数据库引擎为innodb。

 
  1. -- 新建数据库db_user;
  2. create database db_user;
  3. -- 在db_user库中新建user表
  4. create table db_user.user(id int AUTO_INCREMENT PRIMARY KEY,name varchar(50)) engine=innodb;
  5. -- 新建数据库db_account;
  6. create database db_account;
  7. -- 在db_account库中新建account表
  8. create table db_account.account(user_id int,money double) engine=innodb;

另外,在本案例中,db_user库和db_account库是位于同一个mysql实例中的。 

       

案例代码:

    在使用了事务管理器之后,我们通过atomikos提供的UserTransaction接口的实现类com.atomikos.icatch.jta.UserTransactionImp来开启、提交和回滚事务。而不再是使用java.sql.Connection中的setAutoCommit(false)的方式来开启事务。其他JTA规范中定义的接口,开发人员并不需要直接使用。 

 
  1. import com.atomikos.icatch.jta.UserTransactionImp;
  2. import com.atomikos.jdbc.AtomikosDataSourceBean;
  3.  
  4. import javax.transaction.SystemException;
  5. import javax.transaction.UserTransaction;
  6. import java.sql.Connection;
  7. import java.sql.PreparedStatement;
  8. import java.sql.ResultSet;
  9. import java.sql.Statement;
  10. import java.util.Properties;
  11.  
  12. public class AtomikosExample {
  13.  
  14.    private static AtomikosDataSourceBean createAtomikosDataSourceBean(String dbName) {
  15.       // 连接池基本属性
  16.       Properties p = new Properties();
  17.       p.setProperty("url", "jdbc:mysql://localhost:3306/" + dbName);
  18.       p.setProperty("user", "root");
  19.       p.setProperty("password", "your password");
  20.  
  21.       // 使用AtomikosDataSourceBean封装com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
  22.       AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
  23.       //atomikos要求为每个AtomikosDataSourceBean名称,为了方便记忆,这里设置为和dbName相同
  24.       ds.setUniqueResourceName(dbName);
  25.       ds.setXaDataSourceClassName("com.mysql.jdbc.jdbc2.optional.MysqlXADataSource");
  26.       ds.setXaProperties(p);
  27.       return ds;
  28.    }
  29.  
  30.    public static void main(String[] args) {
  31.  
  32.       AtomikosDataSourceBean ds1 = createAtomikosDataSourceBean("db_user");
  33.       AtomikosDataSourceBean ds2 = createAtomikosDataSourceBean("db_account");
  34.  
  35.       Connection conn1 = null;
  36.       Connection conn2 = null;
  37.       PreparedStatement ps1 = null;
  38.       PreparedStatement ps2 = null;
  39.  
  40.       UserTransaction userTransaction = new UserTransactionImp();
  41.       try {
  42.          // 开启事务
  43.          userTransaction.begin();
  44.  
  45.          // 执行db1上的sql
  46.          conn1 = ds1.getConnection();
  47.          ps1 = conn1.prepareStatement("INSERT into user(name) VALUES (?)", Statement.RETURN_GENERATED_KEYS);
  48.          ps1.setString(1, "tianshouzhi");
  49.          ps1.executeUpdate();
  50.          ResultSet generatedKeys = ps1.getGeneratedKeys();
  51.          int userId = -1;
  52.          while (generatedKeys.next()) {
  53.             userId = generatedKeys.getInt(1);// 获得自动生成的userId
  54.          }
  55.  
  56.          // 模拟异常 ,直接进入catch代码块,2个都不会提交
  57. //        int i=1/0;
  58.  
  59.          // 执行db2上的sql
  60.          conn2 = ds2.getConnection();
  61.          ps2 = conn2.prepareStatement("INSERT into account(user_id,money) VALUES (?,?)");
  62.          ps2.setInt(1, userId);
  63.          ps2.setDouble(2, 10000000);
  64.          ps2.executeUpdate();
  65.  
  66.          // 两阶段提交
  67.          userTransaction.commit();
  68.       } catch (Exception e) {
  69.          try {
  70.             e.printStackTrace();
  71.             userTransaction.rollback();
  72.          } catch (SystemException e1) {
  73.             e1.printStackTrace();
  74.          }
  75.       } finally {
  76.          try {
  77.             ps1.close();
  78.             ps2.close();
  79.             conn1.close();
  80.             conn2.close();
  81.             ds1.close();
  82.             ds2.close();
  83.          } catch (Exception ignore) {
  84.          }
  85.       }
  86.    }
  87. }

2、TransactionEssentials与spring、mybatis整合

在pom中添加以下依赖 

 
  1.     org.springframework
  2.     spring-jdbc
  3.     4.3.7.RELEASE
  4.     org.springframework
  5.     spring-context
  6.     4.3.7.RELEASE
  7.     org.mybatis
  8.     mybatis
  9.     3.4.1
  10.     org.mybatis
  11.     mybatis-spring
  12.     1.3.1

新建User实体

 
  1. package com.tianshouzhi.atomikos;
  2. public class User {
  3.    private int id;
  4.    private String name;
  5.    // setters and getters
  6. }

新建Account实例

 
  1. package com.tianshouzhi.atomikos;
  2. public class Account {
  3.    private int userId;
  4.    private double money;
  5.    // setters and getters
  6. }

新建UserMapper接口,为了方便,这里使用了mybatis 注解方式,没有编写映射文件,作用是一样的

 
  1. package com.tianshouzhi.atomikos.mappers.db_user;
  2. import org.apache.ibatis.annotations.Insert;
  3. import com.tianshouzhi.atomikos.User;
  4. import org.apache.ibatis.annotations.Options;
  5. public interface UserMapper {
  6.    @Insert("INSERT INTO user(id,name) VALUES(#{id},#{name})")
  7.    @Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id")
  8.    public void insert(User user);
  9. }

新建AccountMapper接口

 
  1. package com.tianshouzhi.atomikos.mappers.ds_account;
  2. import com.tianshouzhi.atomikos.Account;
  3. import org.apache.ibatis.annotations.Insert;
  4. public interface AccountMapper {
  5.     @Insert("INSERT INTO account(user_id,money) VALUES(#{userId},#{money})")
  6.     public void insert(Account account);
  7. }

新建使用JTA事务的bean,注意在使用jta事务的时候,依然可以使用spring的声明式事务管理

 
  1. package com.tianshouzhi.atomikos;
  2. import com.tianshouzhi.atomikos.mappers.db_user.UserMapper;
  3. import com.tianshouzhi.atomikos.mappers.ds_account.AccountMapper;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.transaction.annotation.Transactional;
  6. public class JTAService {
  7.    @Autowired
  8.    private UserMapper userMapper;//操作db_user库
  9.    @Autowired
  10.    private AccountMapper accountMapper;//操作db_account库
  11.    @Transactional
  12.    public void insert() {
  13.       User user = new User();
  14.       user.setName("wangxiaoxiao");
  15.       userMapper.insert(user);
  16.        
  17.       //    int i = 1 / 0;//模拟异常,spring回滚后,db_user库中user表中也不会插入记录
  18.       Account account = new Account();
  19.       account.setUserId(user.getId());
  20.       account.setMoney(123456789);
  21.       accountMapper.insert(account);
  22.    }
  23. }

编写配置文件spring-atomikos.xml

 
  1.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
  2.        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  3.        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
  4.  
  5.  
  6.     
  7.     
  8.     
  9.           init-method="init" destroy-method="close">
  10.         
  11.         
  12.                   value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
  13.         
  14.             
  15.                 jdbc:mysql://localhost:3306/db_user
  16.                 root
  17.                 shxx12151022
  18.             
  19.         
  20.     
  21.  
  22.     
  23.     
  24.           init-method="init" destroy-method="close">
  25.         
  26.         
  27.                   value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
  28.         
  29.             
  30.                 jdbc:mysql://localhost:3306/db_account
  31.                 root
  32.                 shxx12151022
  33.             
  34.         
  35.     
  36.  
  37.     
  38.     
  39.         
  40.     
  41.  
  42.     
  43.         
  44.     
  45.  
  46.     
  47.     
  48.         
  49.         
  50.         
  51.     
  52.  
  53.     
  54.         
  55.         
  56.         
  57.     
  58.  
  59.     
  60.     
  61.           destroy-method="close">
  62.         
  63.     
  64.  
  65.     
  66.     
  67.         
  68.     
  69.  
  70.     
  71.     
  72.  
  73.     

测试代码

 
  1. package com.tianshouzhi.atomikos;
  2.  
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;
  5.  
  6. public class AtomikosSpringMybatisExample {
  7.    public static void main(String[] args) {
  8.       ApplicationContext context = new ClassPathXmlApplicationContext("spring-atomikos.xml");
  9.       JTAService jtaService = context.getBean("jtaService", JTAService.class);
  10.       jtaService.insert();
  11.    }
  12. }

    建议读者先直接按照上述代码运行,以确定代码执行后,db_user库的user表和db_account的account表中的确各插入了一条记录,以证明我们的代码的确是操作了2个库,属于分布式事务。

    然后将JTAService中的异常模拟的注释打开,会发现出现异常后,两个库中都没有新插入的数据库,说明我们使用的JTA事务管理器的确保证数据的一致性了。

Atomikos配置

    在掌握了Atomikos基本使用之后,我们对Atomikos的配置进行一下简单的介绍。Atomikos在启动后,默认会从以下几个位置读取配置文件,这里笔者直接贴出atomikos源码进行说明:

com.atomikos.icatch.provider.imp.AssemblerImp#initializeProperties方法中定义了配置加载顺序逻辑: 

 
  1. @Override
  2.     public ConfigProperties initializeProperties() {
  3.         //读取classpath下的默认配置transactions-defaults.properties
  4.         Properties defaults = new Properties();
  5.         loadPropertiesFromClasspath(defaults, DEFAULT_PROPERTIES_FILE_NAME);
  6.         //读取classpath下,transactions.properties配置,覆盖transactions-defaults.properties中相同key的值
  7.         Properties transactionsProperties = new Properties(defaults);
  8.         loadPropertiesFromClasspath(transactionsProperties, TRANSACTIONS_PROPERTIES_FILE_NAME);
  9.         //读取classpath下,jta.properties,覆盖transactions-defaults.properties、transactions.properties中相同key的值
  10.         Properties jtaProperties = new Properties(transactionsProperties);
  11.         loadPropertiesFromClasspath(jtaProperties, JTA_PROPERTIES_FILE_NAME);
  12.         
  13.         //读取通过java -Dcom.atomikos.icatch.file方式指定的自定义配置文件路径,覆盖之前的同名配置
  14.         Properties customProperties = new Properties(jtaProperties);
  15.         loadPropertiesFromCustomFilePath(customProperties);
  16.         //最终构造一个ConfigProperties对象,来表示实际要使用的配置
  17.         Properties finalProperties = new Properties(customProperties);
  18.         return new ConfigProperties(finalProperties);
  19.     }

配置文件优先级:transactions-defaults.properties

其中transactions-defaults.properties是atomikos自带的默认配置,位于transactions-xxx.jar中.

atomikos JTA/XA全局事务_第2张图片

注意不同版本的默认配置可能不同。特别是3.x版本和4.x版本的差异比较明显。   

以下是4.0.6中 transactions-default.properties中配置内容,笔者对这些配置进行了归类,如下: 

 
  1. ===============================================================
  2. ============          事务管理器(TM)配置参数       ==============
  3. ===============================================================
  4. #指定是否启动磁盘日志,默认为true。在生产环境下一定要保证为true,否则数据的完整性无法保证
  5. com.atomikos.icatch.enable_logging=true
  6. #JTA/XA资源是否应该自动注册
  7. com.atomikos.icatch.automatic_resource_registration=true
  8. #JTA事务的默认超时时间,默认为10000ms
  9. com.atomikos.icatch.default_jta_timeout=10000
  10. #事务的最大超时时间,默认为300000ms。这表示事务超时时间由 UserTransaction.setTransactionTimeout()较大者决定。4.x版本之后,指定为0的话则表示不设置超时时间
  11. com.atomikos.icatch.max_timeout=300000
  12. #指定在两阶段提交时,是否使用不同的线程(意味着并行)。3.7版本之后默认为false,更早的版本默认为true。如果为false,则提交将按照事务中访问资源的顺序进行。
  13. com.atomikos.icatch.threaded_2pc=false
  14. #指定最多可以同时运行的事务数量,默认值为50,负数表示没有数量限制。在调用 UserTransaction.begin()方法时,可能会抛出一个”Max number of active transactions reached”异常信息,表示超出最大事务数限制
  15. com.atomikos.icatch.max_actives=50
  16. #是否支持subtransaction,默认为true
  17. com.atomikos.icatch.allow_subtransactions=true
  18. #指定在可能的情况下,否应该join 子事务(subtransactions),默认值为true。如果设置为false,对于有关联的不同subtransactions,不会调用XAResource.start(TM_JOIN)
  19. com.atomikos.icatch.serial_jta_transactions=true
  20. #指定JVM关闭时是否强制(force)关闭事务管理器,默认为false
  21. com.atomikos.icatch.force_shutdown_on_vm_exit=false
  22. #在正常关闭(no-force)的情况下,应该等待事务执行完成的时间,默认为Long.MAX_VALUE
  23. com.atomikos.icatch.default_max_wait_time_on_shutdown=9223372036854775807
  24.  
  25. ===============================================================
  26. =========        事务日志(Transaction logs)记录配置       =======
  27. ===============================================================
  28. #事务日志目录,默认为./。
  29. com.atomikos.icatch.log_base_dir=./
  30. #事务日志文件前缀,默认为tmlog。事务日志存储在文件中,文件名包含一个数字后缀,日志文件以.log为扩展名,如tmlog1.log。遇到checkpoint时,新的事务日志文件会被创建,数字增加。
  31. com.atomikos.icatch.log_base_name=tmlog
  32. #指定两次checkpoint的时间间隔,默认为500
  33. com.atomikos.icatch.checkpoint_interval=500
  34.  
  35. ===============================================================
  36. =========          事务日志恢复(Recovery)配置       =============
  37. ===============================================================
  38. #指定在多长时间后可以清空无法恢复的事务日志(orphaned),默认86400000ms
  39. com.atomikos.icatch.forget_orphaned_log_entries_delay=86400000
  40. #指定两次恢复扫描之间的延迟时间。默认值为与com.atomikos.icatch.default_jta_timeout相同
  41. com.atomikos.icatch.recovery_delay=${com.atomikos.icatch.default_jta_timeout}
  42. #提交失败时,再抛出一个异常之前,最多可以重试几次,默认值为5
  43. com.atomikos.icatch.oltp_max_retries=5
  44. #提交失败时,每次重试的时间间隔,默认10000ms
  45. com.atomikos.icatch.oltp_retry_interval=10000
  46.  
  47. ===============================================================
  48. =========          其他       =============================== ==
  49. ===============================================================
  50. java.naming.factory.initial=com.sun.jndi.rmi.registry.RegistryContextFactory
  51. com.atomikos.icatch.client_demarcation=false
  52. java.naming.provider.url=rmi://localhost:1099
  53. com.atomikos.icatch.rmi_export_class=none
  54. com.atomikos.icatch.trust_client_tm=false

当我们想对默认的配置进行修改时,可以在classpath下新建一个jta.properties,覆盖同名的配置项即可。

关于不同版本配置的差异,请参考官方文档:https://www.atomikos.com/Documentation/JtaProperties

打印日志

4.x版本之后,优先尝试使用slf4j,如果没有则尝试使用log4j,如果二者都没有,则使用JUL。

参见:https://www.atomikos.com/Documentation/ConfiguringTheLogs

注意这里是说的是如何配置打印工作日志(work log),而前面说的是事务日志(transactions log),二者不是不同的。

你可能感兴趣的:(Atomikos,数据库)