【分佈式事務系列五】jotm的分佈式案例

#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&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提供的。tomcat

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

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

###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();
}
  1. 第一步:事務攔截器開啓事務

咱們知道加入了@Transactional註解,同時開啓tx:annotation-driven,會對本對象進行代理,加入事務攔截器。在事務攔截器中,獲取javax.transaction.UserTransaction,這裏即org.objectweb.jotm.Current,而後使用它開啓事務,並和當前線程進行綁定,綁定關係數據存放在org.objectweb.jotm.Current中。

  1. 第二步:使用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,同時將該鏈接歸入當前事務中。

  1. 第三步

一旦拋出異常,則須要進行事務的回滾操做。回滾就是將當前事務進行回滾,該事務的回滾會調用和它關聯的全部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();
            }
        }
    }
}
  • 第一步:先經過JNDI方式獲取面向開發人員的UserTransaction事務
  • 第二步:經過JNDI方式獲取dataSource,而後進行sql操做
  • 第三步:使用UserTransaction提交事務
  • 第四步:一旦執行過程當中發生異常,使用UserTransaction回滾事務

##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&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的內容:

輸入圖片說明

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

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

繼續,下面還有:

輸入圖片說明

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

相關文章
相關標籤/搜索