JDBC和JTA事務(wù)區(qū)別
簡單的說 jta是多庫的事務(wù) jdbc是單庫的事務(wù)。
jdbc事務(wù)
JDBC事務(wù)由Connnection對象控制管理,也就是說,事務(wù)管理實際上是在JDBC Connection中實現(xiàn)。事務(wù)周期限于Connection的生命周期。java.sql.Connection提供了兩種事務(wù)模式:自動提交和手工提交。
自動提交:缺省是自動提交。一條對數(shù)據(jù)庫的更新(增/刪/改)代表一項事務(wù)操作,操作成功后,系統(tǒng)將自動調(diào)用commit()來提交,否則將調(diào)用rollback()來回滾。
手工提交:通過調(diào)用setAutoCommit(false)來禁止自動提交。這樣就可把多個數(shù)據(jù)庫操作的表達式作為一個事務(wù),在操作完成后調(diào)用commit()來進行整體提交,其中任何一個操作失敗,都不會執(zhí)行到commit(),并產(chǎn)生異常;此時可在異常捕獲時調(diào)用rollback()進行回滾,以保持多次更新操作后,相關(guān)數(shù)據(jù)的一致性,示例如下:
try {
conn =DriverManager.getConnection(...);
conn.setAutoCommit(false);//禁止自動提交,設(shè)置回滾點
stmt = conn.createStatement();
stmt.executeUpdate(...); //數(shù)據(jù)庫更新操作1
stmt.executeUpdate(...); //數(shù)據(jù)庫更新操作2
conn.commit(); //事務(wù)提交
}catch(Exception ex) {
log.error(...);
try {
conn.rollback(); //操作不成功則回滾
}catch(Exception e) {
log.error(...);
}
}
JDBC 事務(wù)的一個缺點是事務(wù)的范圍局限于一個數(shù)據(jù)庫連接。一個JDBC事務(wù)不能跨越多個數(shù)據(jù)庫。也無法在通過RPC的方式調(diào)用中保證事務(wù)。
jta事務(wù)
由于JDBC無法實現(xiàn)分布式事務(wù)。例如操作不同的數(shù)據(jù)庫或MQ(MQ也可以認為是一個數(shù)據(jù)源),這時候就無法使用JDBC來管理事務(wù):
/** 支付訂單處理 **/
@Transactional(rollbackFor = Exception.class)
public void completeOrder() {
orderDao.update(); // 訂單服務(wù)本地更新訂單狀態(tài)
accountService.update(); // 調(diào)用資金賬戶服務(wù)給資金帳戶加款
pointService.update(); // 調(diào)用積分服務(wù)給積分帳戶增加積分
accountingService.insert(); // 調(diào)用會計服務(wù)向會計系統(tǒng)寫入會計原始憑證
merchantNotifyService.notify(); // 調(diào)用商戶通知服務(wù)向商戶發(fā)送支付結(jié)果通知
}
其中調(diào)用了五個服務(wù),這五個服務(wù)都通過RPC的方式調(diào)用。雖然方法中增加了@Transactional注解,但是由于采用調(diào)用了分布式服務(wù),該事務(wù)并不能達到ACID的效果。
JTA(Java Transaction API)提供了跨數(shù)據(jù)庫連接(或其他JTA資源)的事務(wù)管理能力。JTA事務(wù)管理則由JTA容器實現(xiàn)。一個JTA事務(wù)可以有多個參與者,而一個JDBC事務(wù)則被限定在一個單一的數(shù)據(jù)庫連接。下列任一個Java平臺的組件都可以參與到一個JTA事務(wù)中:JDBC連接、JDO PersistenceManager 對象、JMS 隊列、JMS 主題、企業(yè)JavaBeans(EJB)等。
Spring+Atomikos實現(xiàn)JTA:
添加依賴:
<!-- jta -->
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jdbc</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.11</version>
</dependency>
配置文件applicationContext-jta.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean id="dataSource_main" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
<property name="uniqueResourceName" value="mysql/main" /><!-- 取名任意,但不要重復(fù)-- >
<property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
<property name="xaProperties">
<props>
<prop key="user">yourusername</prop>
<prop key="password">yourpswd</prop>
<prop key="URL">jdbc:mysql://yourdatabasehost/tmg</prop>
</props>
</property>
<property name="minPoolSize" value="1" />
<property name="maxPoolSize" value="3" />
<property name="maxIdleTime" value="60" />
</bean>
<bean id="dataSource_other" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
<property name="uniqueResourceName" value="mysql/other" /><!-- 取名任意,但不要重復(fù)-- >
<property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
<property name="xaProperties">
<props>
<prop key="user">yourusername</prop>
<prop key="password">yourpswd</prop>
<prop key="URL">jdbc:mysql://yourdatabasehost/tmg</prop>
</props>
</property>
<property name="minPoolSize" value="1" />
<property name="maxPoolSize" value="3" />
<property name="maxIdleTime" value="60" />
</bean>
<!-- 配置mybitasSqlSessionFactoryBean -->
<bean id="sqlSessionFactory_main" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource_main" />
<property name="configLocation" value="classpath:config/mybatis.xml"/>
<property name="mapperLocations" value="classpath*:mybatis.mappers.main.*/*.xml" />
</bean>
<bean id="sqlSessionFactory_other" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource_other" />
<property name="configLocation" value="classpath:config/mybatis.xml"/>
<property name="mapperLocations" value="classpath*:mybatis.mappers.other.*/*.xml" />
</bean>
<!-- 配置SqlSessionTemplate -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.tmg.dao.main" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory_main" />
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.tmg.dao.other" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory_other" />
</bean>
<!-- 下面三個Bean可以獨立使用來進行事務(wù)控制 -->
<bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close">
<property name="forceShutdown" value="true" />
</bean>
<bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
<property name="transactionTimeout" value="3000" />
</bean>
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager" ref="atomikosTransactionManager"/>
<property name="userTransaction" ref="atomikosUserTransaction"/>
</bean>
<aop:config proxy-target-class="true">
<aop:advisor pointcut="execution(* *com.tmg.service..*(..))" advice-ref="txAdvice" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="insert*" propagation="REQUIRED" read-only="true" />
<tx:method name="add*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
<tx:method name="save*" propagation="REQUIRED" read-only="true" />
<tx:method name="delete*" propagation="REQUIRED" read-only="true" />
<tx:method name="del*" propagation="REQUIRED" read-only="true" />
<tx:method name="update*" propagation="REQUIRED" read-only="true" />
</tx:attributes>
</tx:advice>
</beans>
Dao層:
Service層:
@Override
public void addAndDel(MybtUser user) {
userMapper_main.insert(user);
userMapper_other.deleteByPrimaryKey(user.getId());
}
測試:
@Test
public void testJta() throws Exception{
MybtUser user = new MybtUser(5,3,"E");
userService.addAndDel(user);
}
把MQ也加入JTA事務(wù):
<bean id="listenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="messageListener" ref="jmsQueueReceiver" />
<property name="destination" ref="queueDestination" />
<property name="sessionTransacted" value="true"/>
<property name="transactionManager" ref="transactionManager"/>
</bean>
這樣消息監(jiān)聽器進行消息接收和對應(yīng)的數(shù)據(jù)庫訪問就會處于同一數(shù)據(jù)庫控制下,當消息接收失敗或數(shù)據(jù)庫訪問失敗都會進行事務(wù)回滾操作。當指定了transactionManager時,消息監(jiān)聽容器將忽略sessionTransacted的值。