#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版本就能夠了,不會產生上述問題。或者直接調用三個參數的構造函數,對於第三個參數給出一個空的實現。