本文實(shí)例講述了Spring中的事務(wù)管理。分享給大家供大家參考。具體分析如下:
事務(wù)簡介:
事務(wù)管理是企業(yè)級應(yīng)用程序開發(fā)中必不可少的技術(shù),用來確保數(shù)據(jù)的完整性和一致性
事務(wù)就是一系列的動作,它們被當(dāng)作一個單獨(dú)的工作單元。這些動作要么全部完成,要么全部不起作用
事務(wù)的四個關(guān)鍵屬性(ACID)
① 原子性(atomicity):事務(wù)室一個原子操作,有一系列動作組成。事務(wù)的原子性確保動作要么全部完成,要么完全不起作用
② 一致性(consistency):一旦所有事務(wù)動作完成,事務(wù)就被提交。數(shù)據(jù)和資源就處于一種滿足業(yè)務(wù)規(guī)則的一致性狀態(tài)中
③ 隔離性(isolation):可能有許多事務(wù)會同時處理相同的數(shù)據(jù),因此每個事物都應(yīng)該與其他事務(wù)隔離開來,防止數(shù)據(jù)損壞
④ 持久性(durability):一旦事務(wù)完成,無論發(fā)生什么系統(tǒng)錯誤,它的結(jié)果都不應(yīng)該受到影響。通常情況下,事務(wù)的結(jié)果被寫到持久化存儲器中
Spring中的事務(wù)管理
作為企業(yè)級應(yīng)用程序框架,Spring在不同的事務(wù)管理API之上定義了一個抽象層。而應(yīng)用程序開發(fā)人員不必了解底層的事務(wù)管理API,就可以使用Spring的事務(wù)管理機(jī)制。
Spring既支持編程式事務(wù)管理,也支持聲明式的事務(wù)管理
編程式事務(wù)管理:將事務(wù)管理代碼嵌入到業(yè)務(wù)方法中來控制事務(wù)的提交和回滾,在編程式事務(wù)中,必須在每個業(yè)務(wù)操作中包含額外的事務(wù)管理代碼
聲明式事務(wù)管理:大多數(shù)情況下比編程式事務(wù)管理更好用。它將事務(wù)管理代碼從業(yè)務(wù)方法中分離出來,以聲明的方式來實(shí)現(xiàn)事務(wù)管理。事務(wù)管理作為一種橫切關(guān)注點(diǎn),可以通過AOP方法模塊化。Spring通過Spring AOP框架支持聲明式事務(wù)管理。
Spring事務(wù)的傳播屬性:
當(dāng)事務(wù)方法被另一個事務(wù)方法調(diào)用時,必須指定事務(wù)應(yīng)該如何傳播。例如:方法可能繼續(xù)在現(xiàn)有事務(wù)中運(yùn)行,也可能開啟一個新事務(wù),并在自己的事務(wù)中運(yùn)行。
事務(wù)的傳播行為可以由傳播屬性指定。Spring定義了7種傳播行為:
傳播行為 |
含義 |
PROPAGATION_MANDATORY |
表示該方法必須在事務(wù)中運(yùn)行,如果當(dāng)前事務(wù)不存在,則會拋出一個異常 |
PROPAGATION_NESTED |
表示如果當(dāng)前已經(jīng)存在一個事務(wù),那么該方法將會在嵌套事務(wù)中運(yùn)行。嵌套的事務(wù)可以獨(dú)立于當(dāng)前事務(wù)進(jìn)行單獨(dú)地提交或回滾。如果當(dāng)前事務(wù)不存在,那么其行為與PROPAGATION_REQUIRED一樣。注意各廠商對這種傳播行為的支持是有所差異的。可以參考資源管理器的文檔來確認(rèn)它們是否支持嵌套事務(wù) |
PROPAGATION_NEVER |
表示當(dāng)前方法不應(yīng)該運(yùn)行在事務(wù)上下文中。如果當(dāng)前正有一個事務(wù)在運(yùn)行,則會拋出異常 |
PROPAGATION_NOT_SUPPORTED |
表示該方法不應(yīng)該運(yùn)行在事務(wù)中。如果存在當(dāng)前事務(wù),在該方法運(yùn)行期間,當(dāng)前事務(wù)將被掛起。如果使用JTATransactionManager的話,則需要訪問TransactionManager |
PROPAGATION_REQUIRED |
表示當(dāng)前方法必須運(yùn)行在事務(wù)中。如果當(dāng)前事務(wù)存在,方法將會在該事務(wù)中運(yùn)行。否則,會啟動一個新的事務(wù) |
PROPAGATION_REQUIRED_NEW |
表示當(dāng)前方法必須運(yùn)行在它自己的事務(wù)中。一個新的事務(wù)將被啟動。如果存在當(dāng)前事務(wù),在該方法執(zhí)行期間,當(dāng)前事務(wù)會被掛起。如果使用JTATransactionManager的話,則需要訪問TransactionManager |
PROPAGATION_SUPPORTS |
表示當(dāng)前方法不需要事務(wù)上下文,但是如果存在當(dāng)前事務(wù)的話,那么該方法會在這個事務(wù)中運(yùn)行 |
其中PROPAGATION_REQUIRED為默認(rèn)的傳播屬性
并發(fā)事務(wù)所導(dǎo)致的問題
在同一個應(yīng)用程序或者不同應(yīng)用程序中的多個事務(wù)在同一個數(shù)據(jù)集上并發(fā)執(zhí)行時,可能會出現(xiàn)許多意外的問題。
并發(fā)事務(wù)所導(dǎo)致的問題可以分為以下三類:
① 臟讀:臟讀發(fā)生在一個事務(wù)讀取了另一個事務(wù)改寫但尚未提交的數(shù)據(jù)時。如果改寫在稍后被回滾了,那么第一個事務(wù)獲取的數(shù)據(jù)就是無效的。
② 不可重復(fù)讀:不可重復(fù)讀發(fā)生在一個事務(wù)執(zhí)行相同的查詢兩次或兩次以上,但是每次都得到不同的數(shù)據(jù)時。這通常是因為另一個并發(fā)事務(wù)在兩次查詢期間更新了數(shù)據(jù)
③ 幻讀:幻讀與不可重復(fù)讀類似。它發(fā)生在一個事務(wù)(T1)讀取了幾行數(shù)據(jù),接著另一個并發(fā)事務(wù)(T2)插入了一些數(shù)據(jù)時。在隨后的查詢中,第一個事務(wù)(T1)就會發(fā)現(xiàn)多了一些原本不存在的記錄
代碼示例
首先是數(shù)據(jù)庫表:
包括book(isbn, book_name, price),account(username, balance),book_stock(isbn, stock)
然后是使用的類:
BookShopDao
package com.yl.spring.tx;
public interface BookShopDao {
//根據(jù)書號獲取書的單價
public int findBookPriceByIsbn(String isbn);
//更新書的庫存,使書號對應(yīng)的庫存-1
public void updateBookStock(String isbn);
//更新用戶的賬戶余額:使username的balcance-price
public void updateUserAccount(String username, int price);
}
BookShopDaoImpl
package com.yl.spring.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository("bookShopDao")
public class BookShopDaoImpl 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 sql2 = "SELECT stock FROM book_stock WHERE isbn = ?";
int stock = JdbcTemplate.queryForObject(sql2, Integer.class, isbn);
if (stock == 0) {
throw new BookStockException("庫存不足!");
}
String sql = "UPDATE book_stock SET stock = stock - 1 WHERE isbn = ?";
JdbcTemplate.update(sql, isbn);
}
@Override
public void updateUserAccount(String username, int price) {
//檢查余額是否不足,若不足,則拋出異常
String sql2 = "SELECT balance FROM account WHERE username = ?";
int balance = JdbcTemplate.queryForObject(sql2, Integer.class, username);
if (balance < price) {
throw new UserAccountException("余額不足!");
}
String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
JdbcTemplate.update(sql, price, username);
}
}
BookShopService
public interface BookShopService {
public void purchase(String username, String isbn);
}
BookShopServiceImpl
package com.yl.spring.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
@Autowired
private BookShopDao bookShopDao;
/**
* 1.添加事務(wù)注解
* 使用propagation 指定事務(wù)的傳播行為,即當(dāng)前的事務(wù)方法被另外一個事務(wù)方法調(diào)用時如何使用事務(wù)。
* 默認(rèn)取值為REQUIRED,即使用調(diào)用方法的事務(wù)
* REQUIRES_NEW:使用自己的事務(wù),調(diào)用的事務(wù)方法的事務(wù)被掛起。
*
* 2.使用isolation 指定事務(wù)的隔離級別,最常用的取值為READ_COMMITTED
* 3.默認(rèn)情況下 Spring 的聲明式事務(wù)對所有的運(yùn)行時異常進(jìn)行回滾,也可以通過對應(yīng)的屬性進(jìn)行設(shè)置。通常情況下,默認(rèn)值即可。
* 4.使用readOnly 指定事務(wù)是否為只讀。 表示這個事務(wù)只讀取數(shù)據(jù)但不更新數(shù)據(jù),這樣可以幫助數(shù)據(jù)庫引擎優(yōu)化事務(wù)。若真的是一個只讀取數(shù)據(jù)庫值得方法,應(yīng)設(shè)置readOnly=true
* 5.使用timeOut 指定強(qiáng)制回滾之前事務(wù)可以占用的時間。
*/
@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
noRollbackFor={UserAccountException.class},
readOnly=true, timeout=3)
@Override
public void purchase(String username, String isbn) {
//1.獲取書的單價
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2.更新書的庫存
bookShopDao.updateBookStock(isbn);
//3.更新用戶余額
bookShopDao.updateUserAccount(username, price);;
}
}
BookStockException
package com.yl.spring.tx;
public class BookStockException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 1L;
public BookStockException() {
super();
// TODO Auto-generated constructor stub
}
public BookStockException(String arg0, Throwable arg1, boolean arg2,
boolean arg3) {
super(arg0, arg1, arg2, arg3);
// TODO Auto-generated constructor stub
}
public BookStockException(String arg0, Throwable arg1) {
super(arg0, arg1);
// TODO Auto-generated constructor stub
}
public BookStockException(String arg0) {
super(arg0);
// TODO Auto-generated constructor stub
}
public BookStockException(Throwable arg0) {
super(arg0);
// TODO Auto-generated constructor stub
}
}
UserAccountException
package com.yl.spring.tx;
public class UserAccountException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 1L;
public UserAccountException() {
super();
// TODO Auto-generated constructor stub
}
public UserAccountException(String arg0, Throwable arg1, boolean arg2,
boolean arg3) {
super(arg0, arg1, arg2, arg3);
// TODO Auto-generated constructor stub
}
public UserAccountException(String arg0, Throwable arg1) {
super(arg0, arg1);
// TODO Auto-generated constructor stub
}
public UserAccountException(String arg0) {
super(arg0);
// TODO Auto-generated constructor stub
}
public UserAccountException(Throwable arg0) {
super(arg0);
// TODO Auto-generated constructor stub
}
}
Cashier
package com.yl.spring.tx;
import java.util.List;
public interface Cashier {
public void checkout(String username, List<String>isbns);
}
CashierImpl。CashierImpl.checkout和bookShopService.purchase聯(lián)合測試了事務(wù)的傳播行為
package com.yl.spring.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 CashierImpl implements Cashier {
@Autowired
private BookShopService bookShopService;
@Transactional
@Override
public void checkout(String username, List<String> isbns) {
for(String isbn : isbns) {
bookShopService.purchase(username, isbn);
}
}
}
測試類:
package com.yl.spring.tx;
import java.util.Arrays;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringTransitionTest {
private ApplicationContext ctx = null;
private BookShopDao bookShopDao = null;
private BookShopService bookShopService = null;
private Cashier cashier = null;
{
ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
bookShopDao = ctx.getBean(BookShopDao.class);
bookShopService = ctx.getBean(BookShopService.class);
cashier = ctx.getBean(Cashier.class);
}
@Test
public void testBookShopDaoFindPriceByIsbn() {
System.out.println(bookShopDao.findBookPriceByIsbn("1001"));
}
@Test
public void testBookShopDaoUpdateBookStock(){
bookShopDao.updateBookStock("1001");
}
@Test
public void testBookShopDaoUpdateUserAccount(){
bookShopDao.updateUserAccount("AA", 100);
}
@Test
public void testBookShopService(){
bookShopService.purchase("AA", "1001");
}
@Test
public void testTransactionPropagation(){
cashier.checkout("AA", Arrays.asList("1001", "1002"));
}
}
希望本文所述對大家的Spring程序設(shè)計有所幫助。