1. 程式人生 > >Java事務--spring+atomikos+tomcat實現分散式事務

Java事務--spring+atomikos+tomcat實現分散式事務

        JTA是J2EE的規範之一,如果使用JTA,我們需要去實現相應介面。tomcat是web伺服器,沒有實現J2EE規範,如果我們需要在tomcat伺服器使用JTA,需要自己去實現JTA的介面。本文介紹如何使用tomcat+JTA實現多資料來源的分散式事務。
        一 選型

tomcat需要使用外掛實現JTA,常用外掛有jotm和atomikos,本文以atomikos為例。jar如下:

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

如果是maven專案,POM配置如下:

<span style="white-space:pre">		</span><dependency>
			<groupId>com.atomikos</groupId>
			<artifactId>transactions-jdbc</artifactId>
			<version>3.9.1</version>
		</dependency>
		<dependency>
			<groupId>javax.transaction</groupId>
			<artifactId>jta</artifactId>
			<version>1.1</version>
		</dependency>
		<dependency>
			<groupId>cglib</groupId>
			<artifactId>cglib-nodep</artifactId>
			<version>3.2.4</version>
		</dependency>

        二 配置資料庫

        我們使用了兩個資料庫做測試,模擬多資料來源的業務場景。資料庫如下:

        兩個資料庫分別是log和user,每個資料庫中建立一張表,分別是log和user,資料庫型別都是MySQL(兩個資料庫型別不一致也可以)。
        三 持久層

        我們直接使用JdbcTemplate實現持久化,程式碼如下:

<span style="font-size:18px;">public class LogDao extends JdbcDaoSupport {
	public boolean insertLog(String id, String content) {
		JdbcTemplate template = getJdbcTemplate();
		template.execute("insert into log values('" + id + "','" + content + "')");
		return true;
	}
}</span>
<span style="font-size:18px;">public class UserDao extends JdbcDaoSupport {
	public boolean insertUser(String id, String name) {
		JdbcTemplate template = getJdbcTemplate();
		template.execute("insert into user values('" + id + "','" + name + "')");
		return true;
	}
}</span>

        持久層建立兩個類,每個類新增一個方法,向資料庫插入一條記錄。
        四 service層

        我們要把事務加到這一層,程式碼如下:

@Transactional
public class UserService {
	private UserDao userDao;
	private LogDao logDao;

	public void saveUser(String id, String name) {
		boolean flag1 = userDao.insertUser(id, name);
		System.out.println("zhang san save success!");
		int i = 1 / 0;  // 製造異常
		boolean flag2 = logDao.insertLog(id, id + "_" + name);
		if(flag1 && flag2){
			System.out.println("儲存成功");
		}
		/*boolean flag2 = userDao.insertUser("2","lisi");
		System.out.println("li si save success!");*/
	}

	public UserDao getUserDao() {
		return userDao;
	}

	public void setUserDao(UserDao userDao) {
		this.userDao = userDao;
	}

	public LogDao getLogDao() {
		return logDao;
	}

	public void setLogDao(LogDao logDao) {
		this.logDao = logDao;
	}
}

        分別呼叫持久層方法,並且在兩次呼叫中間丟擲異常,看資料是否能持久化到資料庫。
        五 配置檔案

        1 資源管理器配置

<!-- 資料庫1 -->
	<bean id="db1" class="com.atomikos.jdbc.SimpleDataSourceBean" init-method="init" destroy-method="close">   
        <property name="uniqueResourceName">   
            <value>mysql/main</value>   
        </property>   
        <property name="xaDataSourceClassName">   
            <value>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</value>   
        </property>   
        <property name="xaDataSourceProperties">   
            <value>URL=jdbc:mysql://localhost:3306/user?useUnicode=true&characterEncoding=utf-8;user=root;password=123456</value>   
        </property>   
        <property name="exclusiveConnectionMode">   
            <value>true</value>   
        </property>   
        <property name="connectionPoolSize">   
            <value>3</value>   
        </property>   
        <property name="validatingQuery">   
            <value>SELECT 1</value>   
        </property>   
    </bean>       
    
    <!-- 資料庫2 -->
    <bean id="db2" class="com.atomikos.jdbc.SimpleDataSourceBean" init-method="init" destroy-method="close">   
        <property name="uniqueResourceName">   
            <value>mysql/secondary</value>   
        </property>   
        <property name="xaDataSourceClassName">      
            <value>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</value>   
        </property>
        <property name="xaDataSourceProperties">   
            <value>URL=jdbc:mysql://localhost:3306/log?useUnicode=true&characterEncoding=utf-8;user=root;password=123456</value>   
        </property>
        <property name="exclusiveConnectionMode">   
            <value>true</value>   
        </property>
        <property name="connectionPoolSize">   
            <value>3</value>   
        </property>
        <property name="validatingQuery">
            <value>SELECT 1</value>   
        </property>   
    </bean>

        2 持久層注入

    <bean id="userDao" class="com.zdp.dao.UserDao">
        <property name="dataSource" ref="db1" />
    </bean>
    
    <bean id="logDao" class="com.zdp.dao.LogDao">
        <property name="dataSource" ref="db2" />
    </bean>
    
    <bean id="userService" class="com.zdp.service.UserService">
        <property name="userDao" ref="userDao" />
        <property name="logDao" ref="logDao" />            
    </bean>

        3 事務管理器配置

    <bean id="userTransactionManager" init-method="init" destroy-method="close"
    	class="com.atomikos.icatch.jta.UserTransactionManager">   
        <property name="forceShutdown" value="true" />   
    </bean>
    
    <bean id="userTransactionImp" class="com.atomikos.icatch.jta.UserTransactionImp">   
        <property name="transactionTimeout" value="300"/>    
    </bean>
    
    <bean id="jtaTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">   
        <property name="transactionManager" ref="userTransactionManager" />
        <property name="userTransaction" ref="userTransactionImp" />
        <property name="allowCustomIsolationLevels" value="true"/> 
    </bean>

        4 使用AOP管理事務

    <tx:advice id="txAdvice" transaction-manager="jtaTransactionManager">
        <tx:attributes>
           <tx:method name="save*" propagation="REQUIRED" rollback-for="Exception"/>
           <tx:method name="add*" propagation="REQUIRED" rollback-for="Exception"/>
           <tx:method name="create*" propagation="REQUIRED" rollback-for="Exception"/>
           <tx:method name="insert*" propagation="REQUIRED" rollback-for="Exception"/>
           <tx:method name="del*" propagation="REQUIRED" rollback-for="Exception"/>
           <tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/>
           <tx:method name="*" read-only="true"/>
       </tx:attributes>
    </tx:advice>
    
    <!-- 事務管理 -->
    <aop:config proxy-target-class="true">
        <aop:advisor pointcut="execution(* com.zdp.service..*.*(..))" advice-ref="txAdvice"/>
    </aop:config>

        六 測試類

public class UserTest {
	@Test
	public void testSave() {
		ApplicationContext cxt = new ClassPathXmlApplicationContext("ApplicationContext.xml");
		UserService us = (UserService) cxt.getBean("userService");
		us.saveUser("1", "zhangsan");
	}
}

        七 總結

        經過測試,速度比較慢,如果系統對效能要求較高,不建議使用。