事務簡介: 事務管理是企業級應用程序開發中必不可少的技術,用來確保數據的完整性和一致性 事務就是一系列的動作,它們被當作一個單獨的工作單元。這些動作要么全部完成,要么全部不起作用 事務的四個關鍵屬性(ACID) 原子性(atomicity):事務室一個原子操作,有一系列動作組成。事務的原子性確保動作要么全部完成,要么完全不起作用 一致性(consistency):一旦所有事務動作完成,事務就被提交。數據和資源就處于一種滿足業務規則的一致性狀態中 隔離性(isolation):可能有許多事務會同時處理相同的數據,因此每個事物都應該與其他事務隔離開來,防止數據損壞 持久性(durability):一旦事務完成,無論發生什么系統錯誤,它的結果都不應該受到影響。通常情況下,事務的結果被寫到持久化存儲器中
Spring中的事務管理 作為企業級應用程序框架,Spring在不同的事務管理API之上定義了一個抽象層。而應用程序開發人員不必了解底層的事務管理API,就可以使用Spring的事務管理機制。
Spring既支持編程式事務管理,也支持聲明式的事務管理 編程式事務管理:將事務管理代碼嵌入到業務方法中來控制事務的提交和回滾,在編程式事務中,必須在每個業務操作中包含額外的事務管理代碼 聲明式事務管理:大多數情況下比編程式事務管理更好用。它將事務管理代碼從業務方法中分離出來,以聲明的方式來實現事務管理。事務管理作為一種橫切關注點,可以通過AOP方法模塊化。Spring通過Spring AOP框架支持聲明式事務管理。
Spring事務的傳播屬性:
當事務方法被另一個事務方法調用時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中運行,也可能開啟一個新事務,并在自己的事務中運行。 事務的傳播行為可以由傳播屬性指定。Spring定義了7種傳播行為:
| 傳播行為 | 含義 |
| PROPAGATION_MANDATORY | 表示該方法必須在事務中運行,如果當前事務不存在,則會拋出一個異常 |
| PROPAGATION_NESTED | 表示如果當前已經存在一個事務,那么該方法將會在嵌套事務中運行。嵌套的事務可以獨立于當前事務進行單獨地提交或回滾。如果當前事務不存在,那么其行為與PROPAGATION_REQUIRED一樣。注意各廠商對這種傳播行為的支持是有所差異的。可以參考資源管理器的文檔來確認它們是否支持嵌套事務 |
| PROPAGATION_NEVER | 表示當前方法不應該運行在事務上下文中。如果當前正有一個事務在運行,則會拋出異常 |
| PROPAGATION_NOT_SUPPORTED | 表示該方法不應該運行在事務中。如果存在當前事務,在該方法運行期間,當前事務將被掛起。如果使用JTATransactionManager的話,則需要訪問TransactionManager |
| PROPAGATION_REQUIRED | 表示當前方法必須運行在事務中。如果當前事務存在,方法將會在該事務中運行。否則,會啟動一個新的事務 |
| PROPAGATION_REQUIRED_NEW | 表示當前方法必須運行在它自己的事務中。一個新的事務將被啟動。如果存在當前事務,在該方法執行期間,當前事務會被掛起。如果使用JTATransactionManager的話,則需要訪問TransactionManager |
| PROPAGATION_SUPPORTS | 表示當前方法不需要事務上下文,但是如果存在當前事務的話,那么該方法會在這個事務中運行 |
其中PROPAGATION_REQUIRED為默認的傳播屬性
并發事務所導致的問題 在同一個應用程序或者不同應用程序中的多個事務在同一個數據集上并發執行時,可能會出現許多意外的問題。 并發事務所導致的問題可以分為以下三類: 臟讀:臟讀發生在一個事務讀取了另一個事務改寫但尚未提交的數據時。如果改寫在稍后被回滾了,那么第一個事務獲取的數據就是無效的。 不可重復讀:不可重復讀發生在一個事務執行相同的查詢兩次或兩次以上,但是每次都得到不同的數據時。這通常是因為另一個并發事務在兩次查詢期間更新了數據 幻讀:幻讀與不可重復讀類似。它發生在一個事務(T1)讀取了幾行數據,接著另一個并發事務(T2)插入了一些數據時。在隨后的查詢中,第一個事務(T1)就會發現多了一些原本不存在的記錄
-----------------------------------------------------代碼示例------------------------------------------------------------------------------------
首先是數據庫表:
包括book(isbn, book_name, price),account(username, balance),book_stock(isbn, stock)
然后是使用的類:
BookShopDao
1 package com.yl.spring.tx; 2 3 public interface BookShopDao { 4 //根據書號獲取書的單價 5 public int findBookPriceByIsbn(String isbn); 6 //更新書的庫存,使書號對應的庫存-1 7 public void updateBookStock(String isbn); 8 //更新用戶的賬戶余額:使username的balcance-price 9 public void updateUserAccount(String username, int price);10 11 }BookShopDaoImpl
1 package com.yl.spring.tx; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.jdbc.core.JdbcTemplate; 5 import org.springframework.stereotype.Repository; 6 7 @Repository("bookShopDao") 8 public class BookShopDaoImpl implements BookShopDao { 9 10 @Autowired11 private JdbcTemplate JdbcTemplate;12 13 @Override14 public int findBookPriceByIsbn(String isbn) {15 String sql = "SELECT price FROM book WHERE isbn = ?";16 17 return JdbcTemplate.queryForObject(sql, Integer.class, isbn);18 }19 20 @Override21 public void updateBookStock(String isbn) {22 //檢查書的庫存是否足夠,若不夠,則拋出異常23 String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?";24 int stock = JdbcTemplate.queryForObject(sql2, Integer.class, isbn);25 if (stock == 0) {26 throw new BookStockException("庫存不足!");27 }28 String sql = "UPDATE book_stock SET stock = stock - 1 WHERE isbn = ?";29 JdbcTemplate.update(sql, isbn);30 }31 32 @Override33 public void updateUserAccount(String username, int price) {34 //檢查余額是否不足,若不足,則拋出異常35 String sql2 = "SELECT balance FROM account WHERE username = ?";36 int balance = JdbcTemplate.queryForObject(sql2, Integer.class, username);37 if (balance < price) {38 throw new UserAccountException("余額不足!");39 } 40 String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";41 JdbcTemplate.update(sql, price, username);42 }43 44 }BookShopService
1 package com.yl.spring.tx;2 3 public interface BookShopService {4 5 public void purchase(String username, String isbn);6 }BookShopServiceImpl
1 package com.yl.spring.tx; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.stereotype.Service; 5 import org.springframework.transaction.annotation.Isolation; 6 import org.springframework.transaction.annotation.Propagation; 7 import org.springframework.transaction.annotation.Transactional; 8 9 @Service("bookShopService")10 public class BookShopServiceImpl implements BookShopService {11 12 @Autowired13 private BookShopDao bookShopDao;14 15 16 /**17 * 1.添加事務注解18 * 使用propagation 指定事務的傳播行為,即當前的事務方法被另外一個事務方法調用時如何使用事務。19 * 默認取值為REQUIRED,即使用調用方法的事務20 * REQUIRES_NEW:使用自己的事務,調用的事務方法的事務被掛起。21 * 22 * 2.使用isolation 指定事務的隔離級別,最常用的取值為READ_COMMITTED23 * 3.默認情況下 Spring 的聲明式事務對所有的運行時異常進行回滾,也可以通過對應的屬性進行設置。通常情況下,默認值即可。24 * 4.使用readOnly 指定事務是否為只讀。 表示這個事務只讀取數據但不更新數據,這樣可以幫助數據庫引擎優化事務。若真的是一個只讀取數據庫值得方法,應設置readOnly=true25 * 5.使用timeOut 指定強制回滾之前事務可以占用的時間。26 */27 @Transactional(propagation=Propagation.REQUIRES_NEW, 28 isolation=Isolation.READ_COMMITTED, 29 noRollbackFor={UserAccountException.class},30 readOnly=true, timeout=3)31 @Override32 public void purchase(String username, String isbn) {33 //1.獲取書的單價34 int price = bookShopDao.findBookPriceByIsbn(isbn);35 //2.更新書的庫存36 bookShopDao.updateBookStock(isbn);37 //3.更新用戶余額38 bookShopDao.updateUserAccount(username, price);;39 }40 41 }BookStockException
1 package com.yl.spring.tx; 2 3 public class BookStockException extends RuntimeException { 4 5 /** 6 * 7 */ 8 private static final long serialVersionUID = 1L; 9 10 public BookStockException() {11 super();12 // TODO Auto-generated constructor stub13 }14 15 public BookStockException(String arg0, Throwable arg1, boolean arg2,16 boolean arg3) {17 super(arg0, arg1, arg2, arg3);18 // TODO Auto-generated constructor stub19 }20 21 public BookStockException(String arg0, Throwable arg1) {22 super(arg0, arg1);23 // TODO Auto-generated constructor stub24 }25 26 public BookStockException(String arg0) {27 super(arg0);28 // TODO Auto-generated constructor stub29 }30 31 public BookStockException(Throwable arg0) {32 super(arg0);33 // TODO Auto-generated constructor stub34 }35 36 }UserAccountException
1 package com.yl.spring.tx; 2 3 public class UserAccountException extends RuntimeException { 4 5 /** 6 * 7 */ 8 private static final long serialVersionUID = 1L; 9 10 public UserAccountException() {11 super();12 // TODO Auto-generated constructor stub13 }14 15 public UserAccountException(String arg0, Throwable arg1, boolean arg2,16 boolean arg3) {17 super(arg0, arg1, arg2, arg3);18 // TODO Auto-generated constructor stub19 }20 21 public UserAccountException(String arg0, Throwable arg1) {22 super(arg0, arg1);23 // TODO Auto-generated constructor stub24 }25 26 public UserAccountException(String arg0) {27 super(arg0);28 // TODO Auto-generated constructor stub29 }30 31 public UserAccountException(Throwable arg0) {32 super(arg0);33 // TODO Auto-generated constructor stub34 }35 36 }Cashier
1 package com.yl.spring.tx;2 3 import java.util.List;4 5 public interface Cashier {6 7 public void checkout(String username, List<String>isbns);8 }
CashierImpl。CashierImpl.checkout和bookShopService.purchase聯合測試了事務的傳播行為
1 package com.yl.spring.tx; 2 3 import java.util.List; 4 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.stereotype.Service; 7 import org.springframework.transaction.annotation.Transactional; 8 9 @Service("cashier")10 public class CashierImpl implements Cashier {11 @Autowired12 private BookShopService bookShopService;13 14 @Transactional15 @Override16 public void checkout(String username, List<String> isbns) {17 for(String isbn : isbns) {18 bookShopService.purchase(username, isbn);19 }20 21 }22 23 }測試類:
1 package com.yl.spring.tx; 2 3 4 import java.util.Arrays; 5 6 import org.junit.Test; 7 import org.springframework.context.applicationContext; 8 import org.springframework.context.support.ClassPathxmlApplicationContext; 9 10 public class SpringTransitionTest {11 12 private ApplicationContext ctx = null;13 private BookShopDao bookShopDao = null;14 private BookShopService bookShopService = null;15 private Cashier cashier = null;16 17 {18 ctx = new ClassPathXmlApplicationContext("applicationContext.xml");19 bookShopDao = ctx.getBean(BookShopDao.class);20 bookShopService = ctx.getBean(BookShopService.class);21 cashier = ctx.getBean(Cashier.class);22 }23 24 @Test25 public void testBookShopDaoFindPriceByIsbn() {26 System.out.println(bookShopDao.findBookPriceByIsbn("1001"));27 }28 29 @Test30 public void testBookShopDaoUpdateBookStock(){31 bookShopDao.updateBookStock("1001");32 }33 34 @Test35 public void testBookShopDaoUpdateUserAccount(){36 bookShopDao.updateUserAccount("AA", 100);37 }38 @Test39 public void testBookShopService(){40 bookShopService.purchase("AA", "1001");41 }42 43 @Test44 public void testTransactionPropagation(){45 cashier.checkout("AA", Arrays.asList("1001", "1002"));46 }47 48 }
新聞熱點
疑難解答