#0 系列目录#java
#1 与Spring集成方式使用jotm# 工程代码地址:与Spring集成方式使用jotm,先来感觉下一个分布式事务的案例(使用通常的数据库驱动,不须要支持分布式XA协议):mysql
##1.1 业务逻辑的操做## UserDao和LogDao,操做分别以下:git
@Repository public class UserDao { @Resource(name="jdbcTemplateA") private JdbcTemplate jdbcTemplate; public void save(User user){ jdbcTemplate.update("insert into user(name,age) values(?,?)",user.getName(),user.getAge()); } } @Repository public class LogDao { @Resource(name="jdbcTemplateB") private JdbcTemplate jdbcTemplate; public void save(User user){ jdbcTemplate.update("insert into log(name,age) values(?,?)",user.getName(),user.getAge()); }
即上述两个JdbcTemplate使用不一样的数据库。web
UserService综合上述两个业务操做,使它们处于同一个事务中:spring
@Service public class UserService { @Autowired private UserDao userDao; @Autowired private LogDao logDao; @Transactional public void save(User user){ userDao.save(user); logDao.save(user); throw new RuntimeException(); } }
##1.2 配置## 上述业务代码咱们看不到分布式事务的存在,这种正是咱们想要的效果,分布式事务对业务透明。究竟是如何来实现呢?sql
###1.2.1 dataSource和JdbcTemplate配置###数据库
<bean id="dataSourceA" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource" destroy-method="shutdown"> <property name="dataSource"> <bean class="org.enhydra.jdbc.standard.StandardXADataSource" destroy-method="shutdown"> <property name="transactionManager" ref="jotm" /> <property name="driverName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8" /> </bean> </property> <property name="user" value="root" /> <property name="password" value="xxxxx" /> </bean> <bean id="dataSourceB" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource" destroy-method="shutdown"> <property name="dataSource"> <bean class="org.enhydra.jdbc.standard.StandardXADataSource" destroy-method="shutdown"> <property name="transactionManager" ref="jotm" /> <property name="driverName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8" /> </bean> </property> <property name="user" value="root" /> <property name="password" value="xxxx" /> </bean>
自行配置2个数据库地址,咱们日常使用的dataSource,大部分是c3p0、dbcp等,这里就不能使用它们了,须要换成能够模拟XA协议的dataSource,即StandardXAPoolDataSource
。这个链接池是xapool提供的。tomcat
顺便简单介绍下xapool官网地址:分布式
它能够经过使用普通的数据库驱动来模拟两阶段提交协议中XAResource的做用
。原本XAResource是须要由数据库XA驱动来实现的;###1.2.2 事务配置### 咱们知道分布式事务中须要一个事务管理器即接口javax.transaction.TransactionManager、面向开发人员的javax.transaction.UserTransaction。对于jotm来讲,他们的实现类都是Current
,以下源码所示:函数
public class Current implements UserTransaction, TransactionManager
咱们若是想使用分布式事务的同时,又想使用Spring带给咱们的@Transactional便利,就须要配置一个JtaTransactionManager
,而该JtaTransactionManager是须要一个userTransaction实例的,因此用到了上面的Current,以下配置:
<bean id="jotm" class="org.objectweb.jotm.Current" /> <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="userTransaction" ref="jotm" /> </bean> <tx:annotation-driven transaction-manager="transactionManager"/>
同时上述StandardXADataSource是须要一个TransactionManager实例的,因此上述StandardXADataSource配置把jotm加了进去。
###1.2.3 jar包依赖### 整个工程主要是利用jotm和xapool来实现分布式事务。jotm提供事务管理器javax.transaction.TransactionManager和面向开发人员的功能接口javax.transaction.UserTransaction
,而xapool则是对非XA数据库驱动进行包装,来模拟XA数据库驱动干的事
。因此依赖的pom以下:
<!-- jotm --> <dependency> <groupId>org.ow2.jotm</groupId> <artifactId>jotm-core</artifactId> <version>2.3.1-M1</version> </dependency> <dependency> <groupId>org.ow2.jotm</groupId> <artifactId>jotm-datasource</artifactId> <version>2.3.1-M1</version> </dependency> <!-- xapool --> <dependency> <groupId>com.experlog</groupId> <artifactId>xapool</artifactId> <version>1.5.0</version> </dependency>
上述jotm-datasource主要是为了将上述StandardXAPoolDataSource数据源放置到容器中
,如tomcat,而不是应用程序中。应用程序经过JNDI的方式从tomcat容器中获取上述数据源
。因此对于本工程来讲能够不要,对于下文说的经过JNDI方式使用jotm则是必须的
。
##1.3 分布式事务执行的大体过程## 下面先粗略的说明下分布式事务的大体执行过程,即下面的执行过程:
@Transactional public void save(User user){ userDao.save(user); logDao.save(user); throw new RuntimeException(); }
咱们知道加入了@Transactional注解,同时开启tx:annotation-driven,会对本对象进行代理,加入事务拦截器。在事务拦截器中,获取javax.transaction.UserTransaction,这里即org.objectweb.jotm.Current,而后使用它开启事务,并和当前线程进行绑定,绑定关系数据存放在org.objectweb.jotm.Current中。
jdbcTemplateA要从dataSourceA中获取Connection,和当前线程进行绑定,同时以对应的dataSourceA做为key。同时判断当前线程是否含有事务,经过dataSourceA中的org.objectweb.jotm.Current发现当前线程有事务,则把Connection自动提交设置为false,同时将该链接归入当前事务中。
jdbcTemplateB要从dataSourceB中获取Connection,和当前线程进行绑定,同时以对应的dataSourceB做为key。同时判断当前线程是否含有事务,经过dataSourceB中的org.objectweb.jotm.Current发现当前线程有事务,则把Connection自动提交设置为false,同时将该链接归入当前事务中。
一旦抛出异常,则须要进行事务的回滚操做。回滚就是将当前事务进行回滚,该事务的回滚会调用和它关联的全部Connection的回滚。
这里再举一个简单的例子,以下:
Connection connA=dataSourceA.getConnection(); Connection connB=dataSourceB.getConnection(); Statement statementA=connA.createStatement(); Statement statementB=connB.createStatement(); String sql="insert into user(name,age) values('"+user.getName()+"',"+user.getAge()+")"; try { connA.setAutoCommit(false); connB.setAutoCommit(false); statementA.execute(sql); statementB.execute(sql); //throw new RuntimeException(); connA.commit(); connB.commit(); } catch (Exception e) { e.printStackTrace(); statementA.close(); statementB.close(); connA.rollback(); connB.rollback(); }finally{ connA.close(); connB.close(); }
咱们这样作:把全部的Connection的自动提交都设置为false,一旦执行过程当中发生异常,调用每一个Connection的回滚方法,若是没异常,则所有提交。这样作也能够实现分布式事务操做
。
jotm也是一样的思路,在上述工程中,使用jdbcTemplate操做,就会把使用的Connection的自动提交设置为false,同时把这个Connection交给事务管理,一旦抛出异常,事务就会把它拥有的全部Connection所有回滚
。
#2 经过JNDI方式使用jotm# 工程代码地址:经过JNDI方式使用jotm,再介绍下经过JNDI方式如何来使用jotm,以及碰到的最新版jotm-core中的一个bug。
##2.1 操做代码##
public void save(User user){ UserTransaction userTransaction=null; try { Context ctx = new InitialContext(); DataSource dataSourceA = (DataSource) ctx.lookup("java:comp/env/jdbc/dataSourceA"); DataSource dataSourceB = (DataSource) ctx.lookup("java:comp/env/jdbc/dataSourceB"); userTransaction = (UserTransaction) ctx.lookup("java:comp/UserTransaction"); userTransaction.begin(); Connection connA=dataSourceA.getConnection(); Connection connB=dataSourceB.getConnection(); Statement statementA=connA.createStatement(); Statement statementB=connB.createStatement(); String sqlA="insert into user(name,age) values('"+user.getName()+"',"+user.getAge()+")"; String sqlB="insert into log(name,age) values('"+user.getName()+"',"+user.getAge()+")"; statementA.execute(sqlA); statementB.execute(sqlB); userTransaction.commit(); } catch (Exception e) { e.printStackTrace(); if(userTransaction!=null){ try { userTransaction.rollback(); } catch (IllegalStateException | SecurityException | SystemException e1) { e1.printStackTrace(); } } } }
##2.2 tomcat的JNDI配置## 在tomcat的context.xml配置文件中以下方式配置: ###2.2.1 配置UserTransaction### 配置以下:
<Transaction factory="org.objectweb.jotm.UserTransactionFactory"/>
这个配置默认将"java:comp/UserTransaction"和上述UserTransactionFactory产生的对象关联了起来(还不太了解JNDI的话,须要去补充下知识)。因此能够经过以下方式来获取:
userTransaction = (UserTransaction) ctx.lookup("java:comp/UserTransaction");
咱们来看下,jotm提供的UserTransaction实现是什么对象,即该UserTransactionFactory产生的对象是?
能够看到提供的UserTransaction实现是org.objectweb.jotm.Current。
###2.2.2 配置两个dataSource#### 配置以下:
<Resource name="jdbc/dataSourceA" auth="Container" type="javax.sql.DataSource" factory="org.objectweb.jotm.datasource.DataSourceFactory" username="root" password="ligang" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8"/> <Resource name="jdbc/dataSourceB" auth="Container" type="javax.sql.DataSource" factory="org.objectweb.jotm.datasource.DataSourceFactory" username="root" password="ligang" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8"/>
咱们来仔细研究下,它到底用的是什么dataSource,来看下上述配置的factory即org.objectweb.jotm.datasource.DataSourceFactory的内容:
能够看到这里和spring中的配置文件里基本差很少,多和上面的spring配置文件对比对比。
继续,下面还有:
将StandardXADataSource设置进StandardXAPoolDataSource中。 同时StandardXAPoolDataSource须要设置下事务管理器TransactionManager,经过jotm对象来获取的。
###2.2.3 最新版本的一个bug### 上述事务管理器是从jotm对象获取的,咱们继续看下jotm是如何来的?这里正是jotm-core-2.3.1-M1.jar出现bug的地方:
即在加载DataSourceFactory类的时候,就会建立Jotm,来详细看下2.3.1-M1版本的建立方法:
public Jotm(boolean local, boolean bound) throws NamingException { this(local, bound, null); }
能够看到,这里的第三个参数为null,继续看下第三个参数是干什么的?
咱们能够看到第三个参数为null,会产生运行时异常即空指针异常,没有捕获到继续向上层传递,而DataSourceFactory也没有捕获到,直接致使DataSourceFactory类加载失败。
解决办法就是换成低版本的jotm-core,如2.2.2版本就能够了,不会产生上述问题。或者直接调用三个参数的构造函数,对于第三个参数给出一个空的实现。