分佈式事務系列(3.1)jotm的分佈式案例

#1 系列目錄java

#2 與Spring集成方式使用jotmmysql

工程代碼地址:與Spring集成方式使用jotmgit

先來感覺下一個分佈式事務的案例(使用通常的數據庫驅動,不須要支持分佈式XA協議):web

##2.1 業務邏輯的操做spring

UserDao和LogDao,操做分別以下:sql

@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使用不一樣的數據庫。數據庫

UserService綜合上述兩個業務操做,使它們處於同一個事務中:tomcat

[@Service](http://my.oschina.net/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();
	}
}

##2.2 配置分佈式

上述業務代碼咱們看不到分佈式事務的存在,這種正是咱們想要的效果,分佈式事務對業務透明。究竟是如何來實現呢?函數

###2.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&amp;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&amp;characterEncoding=utf-8" />  
        </bean>  
    </property>     
    <property name="user" value="root" />  
    <property name="password" value="xxxx" /> 
</bean>

自行配置2個數據庫地址

咱們日常使用的dataSource,大部分是c3p0、dbcp等,這裏就不能使用它們了,須要換成能夠模擬XA協議的dataSource,即StandardXAPoolDataSource。這個鏈接池是xapool提供的。

順便簡單介紹下xapool官網地址:

  • 它設計了一個GenericPool,這個pool裏面能夠存聽任何Object
  • 它提供了dataSource的實現,同時還提供了針對分佈式的dataSource即StandardXAPoolDataSource,它能夠經過使用普通的數據庫驅動來模擬兩階段提交協議中XAResource的做用。原本XAResource是須要由數據庫XA驅動來實現的。
  • 不過很久都沒更新了,官網上最近一次更新仍是06年

以後再詳細介紹它的源碼內容。

###2.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加了進去

###2.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則是必須的

##2.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中

  • 第二步:使用jdbcTemplate進行業務操做

    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所有回滾。

#3 經過JNDI方式使用jotm

工程代碼地址:經過JNDI方式使用jotm

再介紹下經過JNDI方式如何來使用jotm,以及碰到的最新版jotm-core中的一個bug。

##3.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();
			}
		}
	}
}
  • 第一步:先經過JNDI方式獲取面向開發人員的UserTransaction事務
  • 第二步:經過JNDI方式獲取dataSource,而後進行sql操做
  • 第三步:使用UserTransaction提交事務
  • 第四步:一旦執行過程當中發生異常,使用UserTransaction回滾事務

##3.2 tomcat的JNDI配置

在tomcat的context.xml配置文件中以下方式配置:

###3.2.1 配置UserTransaction

配置以下:

<Transaction factory="org.objectweb.jotm.UserTransactionFactory"/>

這個配置默認將"java:comp/UserTransaction"和上述UserTransactionFactory產生的對象關聯了起來(還不太瞭解JNDI的話,須要去補充下知識)。因此能夠經過以下方式來獲取:

userTransaction = (UserTransaction) ctx.lookup("java:comp/UserTransaction")

咱們來看下,jotm提供的UserTransaction實現是什麼對象,即該UserTransactionFactory產生的對象是?

jotm提供的UserTransaction實現

能夠看到提供的UserTransaction實現是org.objectweb.jotm.Current。

###3.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&amp;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&amp;characterEncoding=utf-8"/>

咱們來仔細研究下,它到底用的是什麼dataSource,來看下上述配置的factory即org.objectweb.jotm.datasource.DataSourceFactory的內容:

DataSourceFactory的內容1

能夠看到這裏和spring中的配置文件裏基本差很少,多和上面的spring配置文件對比對比。

  • 建立StandardXADataSource,設置相關參數
  • 建立StandardXAPoolDataSource,設置相關參數

繼續,下面還有:

DataSourceFactory的內容2

將StandardXADataSource設置進StandardXAPoolDataSource中。 同時StandardXAPoolDataSource須要設置下事務管理器TransactionManager,經過jotm對象來獲取的。

###3.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,繼續看下第三個參數是幹什麼的?

jotm的建立

咱們能夠看到第三個參數爲null,會產生運行時異常即空指針異常,沒有捕獲到繼續向上層傳遞,而DataSourceFactory也沒有捕獲到,直接致使DataSourceFactory類加載失敗。

解決辦法就是換成低版本的jotm-core,如2.2.2版本就能夠了,不會產生上述問題。或者直接調用三個參數的構造函數,對於第三個參數給出一個空的實現

#4 結束語

本篇主要說明了與spring集成方式的jotm案例、使用jndi方式的jotm案例。下一篇就該詳細介紹下整個過程的執行原理。提出的問題以下:

  • jotm作了哪方面的工做?
  • xapool作了哪方面的工做?
  • 2pc的過程怎麼體現的?
相關文章
相關標籤/搜索