Spring3核心技術之事務管理機制
阿新 • • 發佈:2019-02-11
文章轉至http://zhou137520.iteye.com/blog/1675199
Spring對事務的解決辦法其實分為2種:程式設計式實現事務,AOP配置宣告式解決方案。
http://jinnianshilongnian.iteye.com/blog/1496953Spring提供了許多內建事務管理器實現,常用的有以下幾種:
- DataSourceTransactionManager:位於org.springframework.jdbc.datasource包中,資料來源事務管理器,提供對單個javax.sql.DataSource事務管理,用於Spring JDBC抽象框架、iBATIS框架的事務管理;
- HibernateTransactionManager:位於org.springframework.orm.hibernate3或者hibernate4包中,提供對單個org.hibernate.SessionFactory事務支援,用於整合Hibernate框架時的事務管理;該事務管理器只支援Hibernate3+版本,且Spring3.0+版本只支援Hibernate 3.2+版本;
- JtaTransactionManager:位於org.springframework.transaction.jta包中,提供對分散式事務管理的支援,並將事務管理委託給Java EE應用伺服器事務管理器;
兩個不依賴於應用伺服器的開源JTA事務實現元件:JOTM和Atomikos Transactions Essentials
具體用法參考http://jinnianshilongnian.iteye.com/blog/1439900
這篇部落格講解了對於JDBC和JTA分別用PlatformTransactionManager實現和使用TransactionTemplate實現程式設計式事務管理:http://jinnianshilongnian.iteye.com/blog/1441271
程式設計式實現事務
Spring提供兩種程式設計式事務支援:直接使用PlatformTransactionManager實現和使用TransactionTemplate模板類,用於支援邏輯事務管理。
如果採用程式設計式事務推薦使用TransactionTemplate模板類。
Spring框架支援事務管理的核心是事務管理器抽象,對於不同的資料訪問框架(如Hibernate)通過實現策略介面PlatformTransactionManager,從而能支援各種資料訪問框架的事務管理,PlatformTransactionManager介面定義如下:
public interface PlatformTransactionManager {
//返回一個已經啟用的事務或建立一個新的事務
TransactionStatus getTransaction(TransactionDefinition definition)
throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
關於TransactionDefinition介面和TransactionStatus介面: http://jinnianshilongnian.iteye.com/blog/1439900
Spring宣告式事務
在日常開發中,用的最多的就是宣告式事務了,下面將介紹SpringJdbc的宣告式事務的配置方法:
<context:component-scan base-package="com.chou.spring.jdbc"/>
<!-- 配置資料來源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<!-- Connection Info -->
<property name="driverClass" value="${db.driverClass}" />
<property name="jdbcUrl" value="${db.url}" />
<property name="user" value="${db.username}" />
<property name="password" value="${db.password}" />
<!-- Connection Pooling Info -->
<property name="initialPoolSize" value="1" />
<property name="minPoolSize" value="1" />
<property name="maxPoolSize" value="15" />
<property name="maxIdleTime" value="1800" />
<property name="maxStatements" value="0" />
</bean>
<bean id="jdbcTemplateDao" class="com.chou.spring.jdbc.dao.JdbcTemplateDao" >
<property name="dataSource" ref="dataSource" />
</bean>
<!-- JDBC事務管理器 -->
<bean id="jdbctTxManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:advice id="txAdvice" transaction-manager="jdbctTxManager">
<tx:attributes>
<tx:method name="domain*"/>
</tx:attributes>
</tx:advice>
<aop:config proxy-target-class="true">
<aop:advisor advice-ref="txAdvice"
pointcut="execution(* com.chou.spring.jdbc.service.JdbcTemplateService.*(..))"/>
</aop:config>
public class JdbcTemplateDao extends JdbcDaoSupport{
public void save() {
String insertSql = "insert into tab_item values(?,?,?)";
Assert.isTrue(getJdbcTemplate().update(insertSql, new Object[]{6, "HP", "PT540"}) == 1, "插入失敗");
}
public void delete() {
String deleteSql = "delete tab_item where id = ?";
Assert.isTrue(getJdbcTemplate().update(deleteSql, new Object[]{6}) == 1, "刪除失敗");
}
public void update() {
String updateSql = "update tab_item set itemno = ?, itemname = ? where id = ?";
Assert.isTrue(getJdbcTemplate().update(updateSql, new Object[]{"HP", "PT555", 6}) == 1, "修改失敗");
}
}
/**
*
* @author Chou
* @since 2012-9-9
* 把事務定義在Service層是為了避免報錯:
* All calls to this method via a proxy will be routed directly to the proxy.
* 這是是事務轉移問題,你如果在控制層加入事務就不會有提示了,也沒有警告,
* 一般很多人在final DAO里加入事務那是有警告的,
* 如果配置檔案定義了AOP獲取代理物件是proxy-target-class="true"即採用CGLIB方式
* 而cglib動態代理是利用asm開源包,對代理物件類的class檔案載入進來,
* 通過修改其位元組碼生成子類並繼承你寫的類,然後在你的基礎上加事物管理,
* 而JdbcDaoSupport中的setDataSource是final的他繼承不了
* 當然你可以無視它,也沒有問題。
*/
@Service
public class JdbcTemplateService {
@Autowired
private JdbcTemplateDao jdbcTemplateDao;
public void domain(){
jdbcTemplateDao.save();
int i = 2/0;//這裡出錯了,事務就會回滾,之前的save就無效了
jdbcTemplateDao.update();
jdbcTemplateDao.delete();
}
}
//main方法
String[] configLocations = new String[] {"applicationContext.xml"};
ApplicationContext ctx = new ClassPathXmlApplicationContext(configLocations);
JdbcTemplateService j = ctx.getBean(JdbcTemplateService.class);
j.domain();
<tx:advice/>配置詳解
<tx:advice id="……" transaction-manager="……">
<tx:attributes>
<tx:method name="*"
propagation="REQUIRED"
isolation="DEFAULT"
timeout="-1"
read-only="true"
no-rollback-for=""
rollback-for="java.lang.Exception"/>
</tx:attributes>
</tx:advice>
<!-- 最常用的配置 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="create*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="merge*" propagation="REQUIRED" />
<tx:method name="del*" propagation="REQUIRED" />
<tx:method name="remove*" propagation="REQUIRED" />
<tx:method name="put*" propagation="REQUIRED" />
<tx:method name="get*" propagation="SUPPORTS" read-only="true" />
<tx:method name="count*" propagation="SUPPORTS" read-only="true" />
<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
<tx:method name="list*" propagation="SUPPORTS" read-only="true" />
<tx:method name="*" propagation="SUPPORTS" read-only="true" />
<tx:method name="batchSaveOrUpdate" propagation="REQUIRES_NEW" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* cn.javass..service.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" />
</aop:config>
XML形式的事務配置<tx:method >的屬性詳解
屬性 | 型別 | 預設值 | 說明 |
propagation | Propagation列舉 | REQUIRED | 事務傳播屬性 |
isolation | isolation列舉 | DEFAULT(所用資料庫預設級別) | 事務隔離級別 |
readOnly | boolean | false | 是否才用優化的只讀事務 |
timeout | int | -1 | 超時(秒) |
rollbackFor | Class[] | {} | 需要回滾的異常類 |
rollbackForClassName | String[] | {} | 需要回滾的異常類名 |
noRollbackFor | Class[] | {} | 不需要回滾的異常類 |
noRollbackForClassName | String[] | {} | 不需要回滾的異常類名 |
readOnly
事務屬性中的readOnly標誌表示對應的事務應該被最優化為只讀事務。如果值為true就會告訴Spring我這個方法裡面沒有insert或者update,你只需要提供只讀的資料庫Connection就行了,這種執行效率會比read-write的Connection高,所以這是一個最優化提示。在一些情況下,一些事務策略能夠起到顯著的最優化效果,例如在使用Object/Relational對映工具(如:Hibernate或TopLink)時避免dirty checking(試圖“重新整理”)。
timeout
在屬性中還有定義“timeout”值的選項,指定事務超時為幾秒。一般不會使用這個屬性。在JTA中,這將被簡單地傳遞到J2EE伺服器的事務協調程式,並據此得到相應的解釋。
Isolation Level(事務隔離等級)的5個列舉值
為什麼事務要有Isolation Level這個屬性?先回顧下資料庫事務的知識:
第一類丟失更新(lost update):在完全未隔離事務的情況下,兩個事物更新同一條資料資源,某一事物異常終止,回滾造成第一個完成的更新也同時丟失。
第二類丟失更新(second lost updates):是不可重複讀的特殊情況,如果兩個事務都讀取同一行,然後兩個都進行寫操作,並提交,第一個事務所做的改變就會丟失。
髒讀(dirty read):如果第二個事務查詢到第一個事務還未提交的更新資料,形成髒讀。因為第一個事務你還不知道是否提交,所以資料不一定是正確的。
虛讀(phantom read):一個事務執行兩次查詢,第二次結果集包含第一次中沒有或者某些行已被刪除,造成兩次結果不一致,只是另一個事務在這兩次查詢中間插入或者刪除了資料造成的。
不可重複讀(unrepeated read):一個事務兩次讀取同一行資料,結果得到不同狀態結果,如中間正好另一個事務更新了該資料,兩次結果相異,不可信任。
具體關於事務機制可以看我以前的部落格:http://zhou137520.iteye.com/admin/blogs/1638574
當遇到以上這些情況時我們可以設定isolation下面這些列舉值:
DEFAULT:採用資料庫預設隔離級別
SERIALIZABLE:最嚴格的級別,事務序列執行,資源消耗最大;
REPEATABLE_READ:保證了一個事務不會修改已經由另一個事務讀取但未提交(回滾)的資料。避免了“髒讀取”和“不可重複讀取”的情況,但是帶來了更多的效能損失。
READ_COMMITTED:大多數主流資料庫的預設事務等級,保證了一個事務不會讀到另一個並行事務已修改但未提交的資料,避免了“髒讀取”。該級別適用於大多數系統。
READ_UNCOMMITTED:保證了讀取過程中不會讀取到非法資料。隔離級別在於處理多事務的併發問題。
關於propagation屬性的7個傳播行為
REQUIRED:指定當前方法必需在事務環境中執行,如果當前有事務環境就加入當前正在執行的事務環境,如果當前沒有事務,就新建一個事務。這是預設值。
SUPPORTS:指定當前方法加入當前事務環境,如果當前沒有事務,就以非事務方式執行。
MANDATORY:指定當前方法必須加入當前事務環境,如果當前沒有事務,就丟擲異常。
REQUIRES_NEW:指定當前方法總是會為自己發起一個新的事務,如果發現當前方法已執行在一個事務中,則原有事務被掛起,我自己建立一個屬於自己的事務,直我自己這個方法commit結束,原先的事務才會恢復執行。
NOT_SUPPORTED:指定當前方法以非事務方式執行操作,如果當前存在事務,就把當前事務掛起,等我以非事務的狀態執行完,再繼續原來的事務。
NEVER:指定當前方法絕對不能在事務範圍內執行,如果方法在某個事務範圍內執行,容器就拋異常,只有沒關聯到事務,才正常執行。
NESTED:指定當前方法執行時,如果已經有一個事務存在,則執行在這個巢狀的事務中.如果當前環境沒有執行的事務,就新建一個事務,並與父事務相互獨立,這個事務擁有多個可以回滾的保證點。就是指我自己內部事務回滾不會對外部事務造成影響,只對DataSourceTransactionManager事務管理器起效。
註解形式@Transactional實現事務管理
注意@Transactional只能被應用到public方法上,對於其它非public的方法,如果標記了@Transactional也不會報錯,但方法沒有事務功能。
預設情況下,一個有事務的方法,遇到RuntiomeException時會回滾。遇到受檢查的異常是不會回滾的,要想所有異常都回滾,要加上屬性rollbackFor={Exception.class}
<!-- 事務管理器配置 -->
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- 使用annotation定義事務 -->
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" />
transaction-manager:指定事務管理器名字,預設為transactionManager,當使用其他名字時需要明確指定;
proxy-target-class:預設false表示使用JDK代理,如果為true將使用CGLIB代理
order:定義事務通知順序,預設Ordered.LOWEST_PRECEDENCE,表示將順序決定權交給AOP來處理。
建議只在實現類或實現類的方法上使用@Transactional,而不要在介面上使用,這是因為如果使用JDK代理機制是沒問題,因為其使用基於介面的代理;而使用使用CGLIB代理機制時就會遇到問題,因為其使用基於類的代理而不是介面,這是因為介面上的@Transactional註解是“不能繼承的”。
http://jinnianshilongnian.iteye.com/blog/1508018這篇部落格講解了基於JDK動態代理和CGLIB動態代理的實現Spring註解管理事務(@Trasactional)到底有什麼區別。
@Transactional//放在這裡表示所有方法都加入事務管理
public class AnnotationUserServiceImpl implements IUserService {
private IUserDao userDao;
private IAddressService addressService;
@Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED)
public void save(UserModel user) {
userDao.save(user);
user.getAddress().setUserId(user.getId());
addressService.save(user.getAddress());
}
@Transactional(propagation=Propagation.REQUIRED, readOnly=true,
isolation=Isolation.READ_COMMITTED)
public int countAll() {
return userDao.countAll();
}
//setter...
}
總結
程式設計式事務是不推薦的,即使有很少事務操作,Spring發展到現在,沒有理由使用程式設計式事務,只有在為了深入理解Spring事務管理才需要學習程式設計式事務使用。
推薦使用宣告式事務,而且強烈推薦使用<tx:tags>方式的宣告式事務,因為其是無侵入程式碼的,可以配置模板化的事務屬性並運用到多個專案中。
而@Transaction註解事務,不過如果一個專案模組太多,service方法太多導致每個方法都要手動去加註解,是不是很麻煩,也容易出錯。如果一個專案結構清晰,分層明確,那麼標籤形式的配置將是最直觀和方便的辦法。
總之,能保證專案正常工作的事務配置就是最好的。