Spring+Hibernate+Atomikos集成構建JTA的分佈式事務--解決多數據源跨庫事務

一、概念

分佈式事務
分佈式事務是指事務的參與者、支持事務的服務器、資源服務器以及事務管理器分別位於不同的分佈式系統的不同節點之上。簡言之,同時操作多個數據庫保持事務的統一,達到跨庫事務的效果。

JTA
JTA,即Java Transaction API,JTA允許應用程序執行分佈式事務處理———在兩個或多個網絡計算機資源上訪問並且更新數據。JDBC驅動程序的JTA支持極大地增強了數據訪問能力。

JTA和JTS

Java事務API(JTA:Java Transaction API)和它的同胞Java事務服務(JTS:Java Transaction Service),爲J2EE平臺提供了分佈式事務服務(distributed transaction)。
一個分佈式事務(distributed transaction)包括一個事務管理器(transaction manager)和一個或多個資源管理器(resource manager)。
一個資源管理器(resource manager)是任意類型的持久化數據存儲。
事務管理器(transaction manager)承擔着所有事務參與單元者的相互通訊的責任。

JTA與JDBC

JTA事務比JDBC事務更強大。一個JTA事務可以有多個參與者,而一個JDBC事務則被限定在一個單一的數據庫連接。下列任一個Java平臺的組件都可以參與到一個JTA事務中:JDBC連接、JDO PersistenceManager 對象、JMS 隊列、JMS 主題、企業JavaBeans(EJB)、一個用J2EE Connector Architecture 規範編譯的資源分配器。

JOTM
JOTM (Java Open Transaction Manager)是一個開源獨立的事務管理器,支持分佈式事務,Spring3.X已經不再支持JOTM

Atomikos
Atomikos TransactionsEssentials是一個爲Java平臺提供增值服務的並且開源類事務管理器,支持分佈式事務,本文所採用的技術。

Atomikos是目前在分佈式事務管理中做得相當不錯的開源軟件。有10年以上的經驗,Atomikos保障您的關鍵事務和防止昂貴的數據丟失在發生系統故障或事故中.Atomikos支持XA(全局事務)和NON-XA(非全局事務),NON-XA效率高於XA。本文主要是講XA事件,因爲要在不同的數據庫中操作多張表。

接下來說一下spring.x+hibernate中集成Atomikos構建JTA的分佈式事務

二、Spring.x+Hibernate+Atomikos集成
Spring.x+Hibernate怎麼集成此處省略不說,主要講解Spring.x+Atomikos的集成,操作步驟如下:

1、下載Atomikos,需要以下這些包

atomikos-util-1.0.jar
cglib-nodep-2.2.2.jar
transactions-3.7.0.jar
transactions-api-3.7.0.jar
transactions-jdbc-3.7.0.jar
transactions-jta-3.7.0.jar

2、配置
在applicationContext.xml文件當中作如下配置

複製代碼
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:jaxws="http://cxf.apache.org/jaxws"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx 
           http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
           http://cxf.apache.org/jaxws 
           http://cxf.apache.org/schemas/jaxws.xsd">
    <context:annotation-config />
    
    <!-- 通過註解方式自動掃描需交給sping管理的Bean -->
    <context:component-scan base-package="com.ljq" />
    
    <!-- spring atomikos配置 -->
    <!-- bpm數據源 -->
    <bean id="bpmDataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" 
        init-method="init" destroy-method="close">
        <!-- Set unique name for this DataSource -->  
        <property name="uniqueResourceName"><value>bpm</value></property>
        <!-- Set XADatasource class name-->  
        <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
        <property name="xaProperties">
            <props>
                <prop key="user">root</prop>
                <prop key="password">parami2013</prop>
                <prop key="url">jdbc:mysql://192.168.1.254:3306/parami_bpm</prop>
            </props>
        </property>
        <!-- set properties for datasource connection pool -->  
        <property name="poolSize" value="3" />
        <!-- 管理 Connection 被佔用的時間 -->
        <!-- 如果不設置這個值,Atomikos使用默認的300秒(即5分鐘),那麼在處理大批量數據讀取的時候,一旦超過5分鐘,就會拋出類似 Resultset is close 的錯誤 -->
        <property name="reapTimeout"><value>20000</value></property>  
    </bean>

    <!-- center數據源 -->
    <bean id="centerDataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" 
        init-method="init" destroy-method="close">
        <property name="uniqueResourceName"><value>center</value></property>
        <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
        <property name="xaProperties">
            <props>
                <prop key="user">root</prop>
                <prop key="password">parami2013</prop>
                <prop key="url">jdbc:mysql://192.168.1.254:3306/parami_center</prop>
            </props>
        </property>
        <!-- 連接池裏面連接的個數 -->
        <property name="poolSize" value="3" />
        <!-- 管理 Connection 被佔用的時間 -->
        <!-- 如果不設置這個值,Atomikos使用默認的300秒(即5分鐘),那麼在處理大批量數據讀取的時候,一旦超過5分鐘,就會拋出類似 Resultset is close 的錯誤 -->
        <property name="reapTimeout"><value>20000</value></property>  
    </bean>
    
    <!-- 將hibernate數據源注入sessionFactory -->
    <bean id="bpmSessionFactory"
        class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="bpmDataSource" />
        <property name="packagesToScan">
            <list>
                <value>com.ljq.pojo</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
                <prop key="hibernate.show_sql">true</prop>
                <!-- 慎重(推薦禁止) 固定爲update -->
                <prop key="hibernate.hbm2ddl.auto">update</prop>
                <prop key="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</prop>
            </props>
        </property>
    </bean>
    
    <!-- 將hibernate數據源注入sessionFactory -->
    <bean id="centerSessionFactory"
        class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="centerDataSource" />
        <property name="packagesToScan">
            <list>
                <value>com.ljq.pojo</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
                <prop key="hibernate.show_sql">true</prop>
                <!-- 慎重(推薦禁止) 固定爲update -->
                <prop key="hibernate.hbm2ddl.auto">update</prop>
                <prop key="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</prop>
            </props>
        </property>
    </bean>    

    <!-- atomikos事務管理器 -->
    <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" 
        init-method="init" destroy-method="close">
        <property name="forceShutdown">
            <value>true</value>
        </property>
    </bean>

    <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
        <property name="transactionTimeout" value="300" />
    </bean>

    <!-- spring 事務管理器 -->
    <bean id="springTransactionManager"
        class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManager" ref="atomikosTransactionManager"/>
        <property name="userTransaction" ref="atomikosUserTransaction" />
        <property name="allowCustomIsolationLevels" value="true"/> 
    </bean>

    <!-- 使用annotation定義事務,對於要加入事物的類,只需對該類加 @Transactional  -->
    <tx:annotation-driven transaction-manager="springTransactionManager" />

    <!-- hibernate Dao層模板 -->
    <bean id="bpmHibernateTemplate"
        class="org.springframework.orm.hibernate3.HibernateTemplate">
        <property name="sessionFactory" ref="bpmSessionFactory"></property>
    </bean>
    
    <bean id="centerHibernateTemplate"
        class="org.springframework.orm.hibernate3.HibernateTemplate">
        <property name="sessionFactory" ref="centerSessionFactory"></property>
    </bean>
    
</beans>
複製代碼

3、在src文件夾下面加入一個transactions.properties文件

複製代碼
# SAMPLE PROPERTIES FILE FOR THE TRANSACTION SERVICE
# THIS FILE ILLUSTRATES THE DIFFERENT SETTINGS FOR THE TRANSACTION MANAGER
# UNCOMMENT THE ASSIGNMENTS TO OVERRIDE DEFAULT VALUES;

# Required: factory implementation class of the transaction core.
# NOTE: there is no default for this, so it MUST be specified! 
# 
com.atomikos.icatch.service=com.atomikos.icatch.standalone.UserTransactionServiceFactory

        
# Set base name of file where messages are output 
# (also known as the 'console file').
#
# com.atomikos.icatch.console_file_name = tm.out

# Size limit (in bytes) for the console file;
# negative means unlimited.
#
# com.atomikos.icatch.console_file_limit=-1

# For size-limited console files, this option
# specifies a number of rotating files to 
# maintain.
#
# com.atomikos.icatch.console_file_count=1

# Set the number of log writes between checkpoints
#
# com.atomikos.icatch.checkpoint_interval=500

# Set output directory where console file and other files are to be put
# make sure this directory exists!
#
# com.atomikos.icatch.output_dir = ./

# Set directory of log files; make sure this directory exists!
#
# com.atomikos.icatch.log_base_dir = ./

# Set base name of log file
# this name will be  used as the first part of 
# the system-generated log file name
#
# com.atomikos.icatch.log_base_name = tmlog

# Set the max number of active local transactions 
# or -1 for unlimited.
#
# com.atomikos.icatch.max_actives = 50

# Set the default timeout (in milliseconds) for local transactions
#
# com.atomikos.icatch.default_jta_timeout = 10000

# Set the max timeout (in milliseconds) for local transactions
#
# com.atomikos.icatch.max_timeout = 300000

# The globally unique name of this transaction manager process
# override this value with a globally unique name
#
# com.atomikos.icatch.tm_unique_name = tm
    
# Do we want to use parallel subtransactions? JTA's default
# is NO for J2EE compatibility
#
# com.atomikos.icatch.serial_jta_transactions=true
                    
# If you want to do explicit resource registration then
# you need to set this value to false.
#
# com.atomikos.icatch.automatic_resource_registration=true  
    
# Set this to WARN, INFO or DEBUG to control the granularity
# of output to the console file.
#
# com.atomikos.icatch.console_log_level=WARN
    
# Do you want transaction logging to be enabled or not?
# If set to false, then no logging overhead will be done
# at the risk of losing data after restart or crash.
#
# com.atomikos.icatch.enable_logging=true

# Should two-phase commit be done in (multi-)threaded mode or not?
# Set this to false if you want commits to be ordered according
# to the order in which resources are added to the transaction.
#
# NOTE: threads are reused on JDK 1.5 or higher. 
# For JDK 1.4, thread reuse is enabled as soon as the 
# concurrent backport is in the classpath - see 
# http://mirrors.ibiblio.org/pub/mirrors/maven2/backport-util-concurrent/backport-util-concurrent/
#
# com.atomikos.icatch.threaded_2pc=false

# Should shutdown of the VM trigger shutdown of the transaction core too?
#
# com.atomikos.icatch.force_shutdown_on_vm_exit=false
複製代碼

4、注入HibernateTemplate模板
a、爲bpm庫注入HibernateTemplate模板

複製代碼
public abstract class BpmDaoSupport<T> extends DaoSupport<T> {
    protected HibernateTemplate bpmHibernateTemplate;

    /**
     * HibernateTemplate注入
     * 
     * @param bpmHibernateTemplate
     */
    @Resource
    public void setBpmHibernateTemplate(HibernateTemplate bpmHibernateTemplate) {
        this.bpmHibernateTemplate = bpmHibernateTemplate;
        setHibernateTemplate(bpmHibernateTemplate);
    }

}
複製代碼

b、爲center庫注入HibernateTemplate模板

複製代碼
public abstract class CenterDaoSupport<T> extends DaoSupport<T> {
    protected HibernateTemplate centerHibernateTemplate;

    /**
     * HibernateTemplate注入
     * 
     * @param centerHibernateTemplate
     */
    @Resource
    public void setCenterHibernateTemplate(HibernateTemplate centerHibernateTemplate) {
        this.centerHibernateTemplate = centerHibernateTemplate;
        setHibernateTemplate(centerHibernateTemplate);
    }
}
複製代碼

5、編寫業務邏輯代碼

複製代碼
@Service(value="userServiceBean")
@Transactional
public class UserServiceBean implements UserService {
    @Resource(name="userDaoBean") private UserDao userDao;
    @Resource(name="userRoleDaoBean") private UserRoleDao userRoleDao;
    
    public List<User> queryUser() {
        return userDao.queryUser();
    }
    
    public void save(User user, UserRole ur) {
        userDao.save(user); //a處
        //此處報異常
        System.out.println(1/0); //b處
        ur.setUserId(user.getUid());
        userRoleDao.save(ur); //c處
    }
}
複製代碼

在執行save()方法時會報java.lang.ArithmeticException: / by zero異常,b處的“1/0”有問題,因爲除數不能爲0;
如果事務生效,save()方法會回滾掉,即a處、c處不做save操作,保持事務的原子性;
如果事務不起作用,那麼a處會執行成功,往數據庫添加一條記錄;b處拋出異常終止向下執行;c處這條代碼不執行,導致髒數據的出現。

Atomikos的集成到此就結束了,親!是不是很簡單。

注意
MySQL的驅動需要5.1.10版本以上,低版本會出現如下錯誤:
java.lang.IllegalArgumentException: null source

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章