JdbcTemplate簡介
為了使JDBC更加易於使用,Spring 在 JDBC API 上定義了一個抽象層,以此建立一個 JDBC 存取框架
作為 Spring JDBC 框架的核心,JDBC 模板的設計目的是為不同類型的 JDBC 操作提供模板方法。每個模板方法都能控制整個過程,並允許覆蓋過程中的特定任務。通過這種方式,可以在盡可能保留靈活性的情況下,將數據庫存取工作量降到最低。
在test Schema下創建一張名為 user 的表,表結構如下
#添加c3p0數據庫連接池 jdbc.user=root jdbc.password=000 jdbc.jdbcUrl=jdbc:mysql:///test jdbc.driverClass=com.mysql.jdbc.Driver jdbc.initPoolSize=5 jdbc.maxPoolSize=10
<!-- 導入資源文件 --> <context:property-placeholder location="classpath:db.properties"/> <!-- 配置 c3p0 數據源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.user}"/> <property name="password" value="${jdbc.password}"/> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"/> <property name="driverClass" value="${jdbc.driverClass}"/> <property name="initialPoolSize" value="${jdbc.initPoolSize}"/> <property name="maxPoolSize" value="${jdbc.maxPoolSize}"/> </bean> <!-- 配置 Spring 的 jdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 還可以配置 NamedParameterJdbcTemplate,該對象可以使用具名參數,其沒有無參構造器,所以必須為其構造器指定參數 --> <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"> <constructor-arg ref="dataSource"></constructor-arg> </bean>
package com.bupt.springtest.jdbc; public class User { private int uid; private String name; private int age; public int getUid() { return uid; } public void setUid(int uid) { this.uid = uid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User [uid=" + uid + ", name=" + name + ", age=" + age + "]"; } }
package com.bupt.springtest.jdbc; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.SqlParameterSource; public class JDBCTest { ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml"); JdbcTemplate jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate"); //在jdbcTemplate中使用具名參數 NamedParameterJdbcTemplate namedParameterJdbcTemplate = ctx.getBean(NamedParameterJdbcTemplate.class); /* * 執行INSERT, UPDATE, DELETE */ public void testUpdate() throws SQLException { String sql = "UPDATE user SET name = ? WHERE uid = ?"; jdbcTemplate.update(sql, "jimmy", 6); } /* * 執行批量更新:批量的INSERT, UPDATE, DELETE * 最后一個參數是 Object[]的 List 類型:因為修改一條記錄需要一個Object數組,多條即需要多個Object數組 */ public void testBatchUpdate() { String sql = "INSERT INTO user(uid, name, age) VALUES(?,?,?)"; List<Object[]> batchArgs = new ArrayList<>(); batchArgs.add(new Object[]{7, "A", 28}); batchArgs.add(new Object[]{8, "B", 20}); batchArgs.add(new Object[]{9, "C", 30}); jdbcTemplate.batchUpdate(sql, batchArgs); } /* * 從數據庫獲取一條記錄,實際得到對應的一個對象 * 類跟表對應,對象跟記錄對應 * 調用queryForObject(String sql, RowMapper<User> rowMapper, Object... args)方法 * 1. 其中的RowMapper指定如何映射結果集的行,常用實現類為 BeanPropertyRowMapper * 2. 使用SQL中列的別名完成列名和類的屬性名完成映射 * 如:若表中定義的name為 user_name,而在User類中定義為name,則sql語句應寫為 * "SELECT uid, user_name name, age FROM user WHERE uid = ?" * 3. 不支持級聯屬性,即表中關聯元素不能查詢到其他表中的數據 */ public void testQuery4Object() { String sql = "SELECT * FROM user WHERE uid = ?"; RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class); User user = jdbcTemplate.queryForObject(sql, rowMapper, 1); System.out.println(user); } /* * 查詢實體類的集合 * 注意調用的不是 queryForList 方法 */ public void testQuery4List() { String sql = "SELECT uid, name, age FROM user WHERE uid > ?"; RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class); List<User> users = jdbcTemplate.query(sql, rowMapper, 5); System.out.println(users); } /* * 獲取單個列的值或做統計查詢 */ public void testQueryForObject() { String sql = "SELECT count(uid) FROM user"; // String sql = "SELECT name FROM user WHERE uid = ?"; // String name = jdbcTemplate.queryForObject(sql, String.class, 2); long count = jdbcTemplate.queryForObject(sql, Long.class); System.out.println(count); } /* * 在經典的JDBC用法中, SQL參數是用占位符 ? 表示,並且受到位置的限制。定位參數的問題在於,一旦參數的順序發生變化,就必須改變參數綁定 * 在Spring JDBC 框架中,綁定 SQL 參數的另一種選擇是使用具名參數(named parameter),它可以為參數起名字 * 具名參數:SQL按名字(以冒號開頭)而不是按位置進行指定。具名參數更易於維護,也提升了可讀性。具名參數由框架類在運行時用占位符取代 * 具名參數只在NamedParameterJdbcTemplate中得到支持 * 1. 好處:若有多個參數,則不用去對應位置,直接對應參數名,便於維護 * 2. 缺點:較為麻煩 */ public void testNamedParameterJdbcTemplate() { String sql = "INSERT INTO user VALUES(:id, :nm, :age)"; Map<String, Object> paramMap = new HashMap<>(); paramMap.put("id", 10); paramMap.put("nm", "elle"); paramMap.put("age", 30); namedParameterJdbcTemplate.update(sql, paramMap); } /* * 使用具名參數時,可以使用update(String sql, SqlParameterSource paramSource)方法進行更新操作 * 1. SQL語句中的參數和類的屬性一致 * 2. 使用 SqlParameterSource的 BeanPropertySqlParameterSource 實現類作為參數 */ @Test public void testNamedParameterJdbcTemplate1() { String sql = "INSERT INTO user VALUES(:uid, :name, :age)"; User user = new User(); user.setUid(11); user.setName("lock"); user.setAge(28); SqlParameterSource parameterSource = new BeanPropertySqlParameterSource(user); namedParameterJdbcTemplate.update(sql, parameterSource); } }
Spring 的事務管理
作為企業級應用程序框架,Spring 在不同的事務管理 API 之上定義了一個抽象層。而應用程序開發人員不必了解底層的事務管理 API,就可以使用 Spring 的事務管理機制。
Spring 既支持編程式事務管理,也支持聲明式事務管理。
編程式事務管理:將事務管理代碼嵌入到業務方法中來控制事務的提交和回滾。在編程式管理事務時,必須在每個事物操作中包含額外的事務管理代碼。
聲明式事務管理:大多數情況下比編程式事務管理更好用。它將事務管理代碼從業務方法中分離出來,以聲明的方式來實現事務管理。事務管理作為一種橫切關注點,可以通過 AOP 方法模塊化。Spring 通過 Spring AOP 框架支持聲明式事務。
Spring 從不同的事務管理 API 中抽象了一整套的事務機制。開發人員不必了解底層事務 API,就可以利用這些事務機制。有了這些事務機制,事務管理代碼就能獨立於特定的事務技術了。Spring 的核心事務管理抽象是 org.springframework.transaction.PlatformTransactionManager,它為事務管理封裝了一組獨立於技術的方法。無論使用 Spring 的哪種事務管理策略(編程式或聲明式),事務管理器都是必須的。
通過代碼來說明Spring 對事務的支持,也學習下 JdbcTemplate 正真在實際中如何使用,我們先在test數據庫中新建三張表
account表 book表 book_stock表
<context:component-scan base-package="com.bupt.springtest.tx"></context:component-scan> <!-- 導入資源文件 --> <context:property-placeholder location="classpath:db.properties"/> <!-- 配置c3p0數據源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.user}"/> <property name="password" value="${jdbc.password}"/> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"/> <property name="driverClass" value="${jdbc.driverClass}"/> </bean> <!-- 配置Spring的 jdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean>
package com.bupt.springtest.tx; public interface BookShopDao { //根據書號獲取書的單價 public int findBookPriceByIsbn(String isbn); //更新書的庫存,使書號對應的庫存減1 public void updateBookStock(String isbn); //更新用戶的賬戶余額:使 username 的 balance - price public void updateUserAccount(String username, int price); }
package com.bupt.springtest.tx; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository("bookShopDao") public class BookShopDaoImp implements BookShopDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public int findBookPriceByIsbn(String isbn) { String sql ="SELECT price FROM book WHERE isbn = ?"; return jdbcTemplate.queryForObject(sql, Integer.class, isbn); } @Override public void updateBookStock(String isbn) { //檢查書的庫存是否足夠,若不夠,則拋出異常 String sql1 = "SELECT stock FROM book_stock WHERE isbn = ?"; int stock = jdbcTemplate.queryForObject(sql1, Integer.class, isbn); if(stock <= 0) { throw new RuntimeException("庫存不足!"); } String sql = "UPDATE book_stock SET stock = stock - 1 WHERE isbn = ?"; jdbcTemplate.update(sql, isbn); } @Override public void updateUserAccount(String username, int price) { //驗證余額是否足夠,若不夠,則拋出異常 String sql1 = "SELECT balance FROM account WHERE username = ?"; int balance = jdbcTemplate.queryForObject(sql1, Integer.class, username); if(balance < price) { throw new RuntimeException("余額不足!"); }
String sql = "UPDATE account SET balance = balance - ? WHERE username = ?"; jdbcTemplate.update(sql, price, username); } }
package com.bupt.springtest.tx; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
//對單個操作事務的測試 public class SpringTransactionTest { ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml"); BookShopDao bookShopDao = ctx.getBean(BookShopDao.class); @Test public void testBookShopDaoUpdateBookStock() { bookShopDao.updateBookStock("1001"); } public void testBookShopDaoFindPriceByIsbn() { System.out.println(bookShopDao.findBookPriceByIsbn("1001")); } public void testBookShopDaoUpdateUserAccount(){ bookShopDao.updateUserAccount("Tom", 100); } }
上面代碼分別測試了單個操作事務,現在我們把上面的操作組成一個完整的事務過程,即買書 —> 更新書庫存 —> 更新賬戶余額
package com.bupt.springtest.tx; public interface BookShopService { //買書事務 public void purchase(String username, String isbn); }
package com.bupt.springtest.tx; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service("bookShopService") public class BookShopServiceImp implements BookShopService { @Autowired private BookShopDao bookShopDao; @Override public void purchase(String username, String isbn) { //獲取書的單價 int price = bookShopDao.findBookPriceByIsbn(isbn); //更新書的庫存 bookShopDao.updateBookStock(isbn); //更新用戶余額 bookShopDao.updateUserAccount(username, price); } }
package com.bupt.springtest.tx; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringTransactionTest { ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml"); BookShopService bookShopService = ctx.getBean(BookShopService.class); @Test public void testBookShopService() {
//執行此代碼前,先將account表中的balance改為60,即使用戶余額不足以購買該書 bookShopService.purchase("Tom", "1001"); } }
執行測試之后我們可以看到,這個操作並不滿足事務的要求,因為書的庫存更新了但賬戶里的錢沒有變,及違背了原子性。這種情況下,我們就需要 Spring 的事務管理來幫我們解決。
用事務通知聲明式地管理事務
Spring 允許簡單地使用 @Transactional 注解來標注事務方法。為了將 方法定義為支持事務處理,可以為方法添加 @Transactional 注解,根據 Spring AOP 基於代理機制,只能標注公有方法。可以在方法或者類級別上添加 @Transactional 注解,當把這個注解應用到類上時,這個類中的所有公有方法都會被定義為支持事務處理。
在 bean 配置時,只需要在配置文件中啟用 <tx:annotation-driven> 元素,並為之指定事務管理器就可以了。如果事務處理器名稱是 transactionManager,就可以在 <tx:annotation-driven> 元素中省略 transaction-manager 屬性。這個元素會自動檢測該名稱的事務處理器。
默認情況下,如果被注解的數據庫操作方法發生了 Unchecked Exception 或 Error,所有的數據庫操作將回滾;如果發生的是 Checked Exception,默認情況下數據庫操作還是會提交的。
我們也可以來改變默認的規則:
1. 讓 Checked Exception 也回滾:在整個方法前加上 @Transactional(rollbackFor=Exception.class)
2. 讓 Unchecked Exception 不回滾:@Transactional(noRollbackFor=RuntimeException.class)
3. 不需要事務管理(只查詢)的方法:@Transactional(propagation=Propagation.NOT_SUPPORTED)
注意:如果異常被 try-catch塊捕獲了,事物就不會回滾了,若想讓事務回滾必須再往外拋異常,即在catch模塊中繼續往外拋 Unchecked Exception。
下面用代碼來具體介紹用法
在ApplicationContext.xml文件中增加下面幾行代碼
<!-- 配置事務管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 啟動注解事務 --> <tx:annotation-driven transaction-manager="transactionManager"/>
在 BookShopServiceImp 的 purchase() 方法上增加注解 @Transactional
package com.bupt.springtest.tx; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Service("bookShopService") public class BookShopServiceImp implements BookShopService { @Autowired private BookShopDao bookShopDao; //添加事務注解 @Transactional @Override public void purchase(String username, String isbn) { //獲取書的單價 int price = bookShopDao.findBookPriceByIsbn(isbn); //更新書的庫存 bookShopDao.updateBookStock(isbn); //更新用戶余額 bookShopDao.updateUserAccount(username, price); } }
package com.bupt.springtest.tx; import java.util.Arrays; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
//測試類 public class SpringTransactionTest { ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml"); BookShopService bookShopService = ctx.getBean(BookShopService.class); @Test public void testBookShopService() { bookShopService.purchase("Tom", "1001"); }
由結果可以看出,此時 purchase() 方法滿足事務的要求。
除了使用上述聲明事務的方法, Spring 還可以通過 tx Schema 中定義的 <tx:advice> 元素聲明事務通知。
事務管理其實是一種橫切關注點,聲明了事務通知后,就需要把它與切入點關聯起來。由於事務通知是在 <aop:config> 元素外部聲明的,所以它無法直接與切入點產生關聯。所以必須在 <aop:config> 元素中聲明一個增強器通知與切入點關聯起來。只有公有方法才能通過 Spring AOP 進行事務管理。
下面我們來看配置方法,將前面配置在xml文件中的 <tx:annotation-driven transaction-manager="transactionManager"/> 刪掉,增加如下配置代碼
<!-- 聲明事務通知,讓所有方法都擁有事務 --> <tx:advice id="bookShopTxAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*"/>
<tx:method name="get*" read-only="true"/> </tx:attributes> </tx:advice> <!-- 聲明事務通知需要通知方法(即需要進行管理的方法) --> <aop:config>
<!-- 配置事務切點,把事務切入點和事務屬性關聯起來 --> <aop:pointcut id="bookShopOperation" expression="execution(* com.bupt.springtest.tx.BookShopServiceImp.*(..))"/> <aop:advisor advice-ref="bookShopTxAdvice" pointcut-ref="bookShopOperation"/> </aop:config>
刪除 BookShopServiceImp.purchase() 方法上標注的 @Transactional 注解,測試后也能正常執行事務。
關於 <tx:method/> 屬性的說明
屬性 | 說明 |
name | 方法名的匹配模式,通知根據該模式尋找匹配的方法。該屬性可以使用通配符(*)。也可以聲明為 (xxx*),即代表以xxx開頭的所有方法,表示符合此命名規則的方法作為一個事務(此屬性是必須要有的) |
propagation | 設定事務定義所用的傳播級別(默認值為REQUIRED) |
isolation | 設定事務的隔離級別(默認值為DEFAULT) |
timeout | 指定事務的超時(單位為秒),默認值為-1 |
read-only | 指定事務是否只讀,若為true指示事務是只讀的(一般來講,對於只執行查詢的事務你會將該屬性設為true,如果出現了更新、插入或是刪除語句時只讀事務就會失敗),默認值為 false |
no-rollback-for | 以逗號分隔的異常類的列表,目標方法可以拋出這些異常而不會導致通知執行回滾,如:NullPointException,ArithmeticException... |
rollback-for | 以逗號分隔的異常類列表,當目標方法拋出這些異常時會導致通知執行回滾。默認情況下,該列表為空,因此不在 no-rollback-for列表中的任何運行時異常都會導致回滾 |
Spring 中事務的傳播特性
當一個事務被另外一個事務調用時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中運行,也可能開啟一個新事物,並在自己的事務中運行
事務的傳播行為可以由傳播屬性來指定,Spring 定義了七種類型的傳播行為
傳播屬性 | 描述 |
REQUIRED | 如果有事務在運行,當前的方法就在這個事務內運行,否則,就啟動一個新的事務,並在自己的事務內運行 |
REQUIRES_NEW | 當前的方法必須啟動新的事務,並在它自己的事務內運行。如果有事務正在運行,應該將它掛起來 |
SUPPORTS | 如果有事務在運行,當前的方法就在這個事務內運行,否則它可以不運行在事務中 |
NOT_SUPPORTED | 當前方法不應該運行在事務中,如果有運行的事務,將它掛起來 |
MANDATORY | 當前方法必須運行在事務內部,如果沒有正在運行的事務,就拋出異常 |
NEVER | 當前的方法不應該運行在事務中,如果有運行的事務,就拋出異常 |
NESTED | 如果有事務在運行,當前的方法就應該在這個事務的嵌套事務內運行。否則,就啟動一個新的事務,並在它自己的事務內運行 |
下面通過代碼來介紹傳播特性的用法
配置文件改回 <tx:annotation-driven transaction-manager="transactionManager"/>
package com.bupt.springtest.tx; import java.util.List; //新定義一個接口,表示用戶結算操作 public interface Cashier { public void checkout(String name, List<String> isbn); }
package com.bupt.springtest.tx; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service("cashier") public class CashierImp implements Cashier { @Autowired private BookShopService bookShopService; //定義一個事物為 checkout @Transactional @Override public void checkout(String username, List<String> isbns) { for(String isbn : isbns) { bookShopService.purchase(username, isbn); } } }
為 BookShopServiceImp.purchase 方法上的 @Transactional 注解添加屬性:propagation=Propagation.REQUIRED
@Service("bookShopService") public class BookShopServiceImp implements BookShopService { @Autowired private BookShopDao bookShopDao; //使用propagation指定事務的傳播行為,即當前事務方法被另外一個事物方法調用時 //如何進行事務,默認取值為REQUIRED //添加事務注解 @Transactional(propagation=Propagation.REQUIRED) @Override public void purchase(String username, String isbn) { //獲取書的單價 int price = bookShopDao.findBookPriceByIsbn(isbn); //更新書的庫存 bookShopDao.updateBookStock(isbn); //更新用戶余額 bookShopDao.updateUserAccount(username, price); } }
//測試代碼 public class SpringTransactionTest { ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml"); Cashier cashier = ctx.getBean(Cashier.class); @Test public void testTransactionPropogation() { cashier.checkout("Tom", Arrays.asList("1001", "1002")); } }
當 bookService 的 purchase() 方法被另外一個事務方法 checkout() 調用時,它默認會在現有事務內運行。這個默認的傳播行為就是 REQUIRED。因此從 checkout() 方法的開始到結束這個事務過程中,只有一個事務在執行。這個事務只在 checkout() 方法結束的時候被提交,若此時賬戶余額不夠支付兩本書的價格,就導致用戶一本書都買不了。這個流程可以用圖表示為:
另一種常見的傳播行為是 REQUIRES_NEW ,它表示該方法必須要啟動一個新事物,並在自己的事務內運行。如果有事務在運行,就應該先把它掛起。相應的代碼修改,只需修改 @Transactional 的屬性
@Transactional(propagation=Propagation.REQUIRES_NEW) @Override public void purchase(String username, String isbn){
此時的事務流程可以表示為如下圖,此時若賬戶金錢能買一本但不夠兩本時,會執行能購買的行為,而對不能購買的行為不執行
同樣在xml配置文件中,我們也可以來設置傳播屬性
<tx:advice id="bookShopTxAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="purchase" propagation="REQUIRES_NEW"/>
</tx:attributes>
</tx:advice>
事務的隔離級別
Spring 支持的事務隔離別如下表所示
隔離級別 | 描述 |
DEFAULT | 使用地城數據庫的默認隔離級別,對大多數數據庫來說,默認的隔離級別都是 READ_COMMITED |
READ_UNCOMMITTED | 允許事務讀取未被其他事務提交的變更。臟讀、不可重復讀和幻讀的問題都會出現 |
READ_COMMITTED | 只允許事務讀取已經被其他事務提交的變更。可以避免臟讀,但不可重復讀和幻讀問題仍然可能出現 |
REPEATABLE_READ | 確保事務可以多次從一個字段中讀取相同的值,在這個事務持續期間,禁止其他事務對這個字段進行更新。可以避免臟讀和不可重復讀,但幻讀的問題仍然存在 |
SERIALIZABLE | 確保事務可以沖一個表中讀取相同的行。在這個事務持續期間,禁止其他事務對該表執行插入、更新和刪除操作。所有並發問題都可以避免,但性能低下 |
Oracle 支持 2 種事務隔離級別:READ_COMMITED,SERIALIZABLE
MySql 支持 4 種
用法如下代碼所示
@Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED) public void purchase(String username, String isbn){
<tx:advice id="bookShopTxAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="purchase" propagation="REQUIRES_NEW" isolation="READ_COMMITTED""/> </tx:attributes> </tx:advice>
設置回滾事務屬性
默認情況下只有未檢查異常( RuntimeException 和 Error 類型的異常)會導致事務回滾。而受檢查異常則不會。(通常情況下我們都是取默認值即可)
事務的回滾規則可以通過 @Transactional 注解的 rollbackFor 和 norollbackFor 屬性來定義。這兩個屬性被聲明為 Class[] 類型的,因此可以為這兩個屬性指定多個異常類,也可以使用屬性 rollbackFor=ClassName 和 norollbackFor=ClassName 通過異常類的名字直接指定。
@Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED, rollbackFor={IOException.class, SQLException.class}, noRollbackFor=ArithmeticException.class) public void purchase(String username, String isbn){
我們還可以在 <tx:method/> 元素中指定回滾規則,此時屬性值要填類全名
<tx:advice id="bookShopTxAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="purchase" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.io.IOException, java.sql.SQLExcpetion" no-rollback-for="java.lang.ArithmeticExcpeion"/> </tx:attributes> </tx:advice>
超時和只讀屬性
超時事務屬性:表示事務在強制回滾之前可以保持多久,這樣可以防止長期運行的事務占用資源。由於事務可以在行和表上獲得鎖,因此長事務會占用資源,對整體性能產生影響。
只讀事務屬性:表示這個事務只讀取數據但不更新數據,這樣可以幫助數據庫引擎優化事務。
超時和只讀屬性可以在 @Transactional 注解中定義。超時屬性以秒為單位計算。
@Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED, rollbackFor={IOException.class, SQLException.class}, noRollbackFor=ArithmeticException.class, readOnly=true, timeout=30) public void purchase(String username, String isbn){
也可以在 <tx:method/>元素中進行指定
<tx:advice id="bookShopTxAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="purchase" propagation="REQUIRES_NEW" isolation="READ_COMMITTED" rollback-for="java.io.IOException, java.sql.SQLExcpetion" no-rollback-for="java.lang.ArithmeticExcpeion" timeout="30" read-only="false"/> </tx:attributes> </tx:advice>