Spring4深入理解----事務(宣告式事務和xml配置事務,事務傳播屬性,事務其他屬性(隔離級別&回滾&只讀&過期))
阿新 • • 發佈:2019-01-02
•事務管理是企業級應用程式開發中必不可少的技術,
用來確保資料的完整性和一致性.
•事務就是一系列的動作,它們被當做一個單獨的工作單元.這些動作要麼全部完成,要麼全部不起作用
•事務的四個關鍵屬性(ACID)
–原子性(atomicity):事務是一個原子操作,由一系列動作組成.事務的原子性確保動作要麼全部完成要麼完全不起作用.
–一致性(consistency):一旦所有事務動作完成,事務就被提交.資料和資源就處於一種滿足業務規則的一致性狀態中.
–隔離性(isolation):可能有許多事務會同時處理相同的資料,因此每個事物都應該與其他事務隔離開來,防止資料損壞 .
–永續性(durability):一旦事務完成,無論發生什麼系統錯誤,它的結果都不應該受到影響.通常情況下,事務的結果被寫到持久化儲存器中.
Spring 中的事務管理
•作為企業級應用程式框架, Spring 在不同的事務管理 API 之上定義了一個抽象層.而應用程式開發人員不必瞭解底層的事務管理API,就可以使用Spring的事務管理機制. •Spring 既支援程式設計式事務管理,也支援宣告式的事務管理. •程式設計式事務管理: 將事務管理程式碼嵌入到業務方法中來控制事務的提交和回滾.在程式設計式管理事務時,必須在每個事務操作中包含額外的事務管理程式碼 .
•宣告式事務管理:
大多數情況下比程式設計式事務管理更好用.它將事務管理程式碼從業務方法中分離出來,
以宣告的方式來實現事務管理.事務管理作為一種橫切關注點,可以通過AOP方法模組化.Spring
通過 SpringAOP
框架支援宣告式事務管理.
Spring 中的事務管理器
•Spring 從不同的事務管理API中抽象了一整套的事務機制.開發人員不必瞭解底層的事務API,就可以利用這些事務機制.有了這些事務機制, 事務管理程式碼就能獨立於特定的事務技術了. •Spring 的核心事務管理抽象是 Interface PlatFormTransactionManager 它為事務管理封裝了一組獨立於技術的方法.無論使用Spring的哪種事務管理策略(程式設計式或宣告式),事務管理器都是必須的.
•除了在帶有切入點,通知和增強器的Bean配置檔案中宣告事務外,Spring 還允許簡單地用 @Transactional註解來標註事務方法. •為了將方法定義為支援事務處理的, 可以為方法新增@Transactional 註解. 根據SpringAOP 基於代理機制, 只能標註公有方法. •可以在方法或者類級別上新增 @Transactional註解.當把這個註解應用到類上時,這個類中的所有公共方法都會被定義成支援事務處理的. •在 Bean 配置檔案中只需要啟用<tx:annotation-driven>元素,併為之指定事務管理器就可以了. •如果事務處理器的名稱是 transactionManager,就可以在<tx:annotation-driven>元素中省略transaction-manager屬性.這個元素會自動檢測該名稱的事務處理器.
REQUIRED 傳播行為
•當bookService的purchase()方法被另一個事務方法checkout()呼叫時,它預設會在現有的事務內執行.這個預設的傳播行為就是REQUIRED.因此在checkout()方法的開始和終止邊界內只有一個事務.這個事務只在checkout()方法結束的時候被提交,結果使用者一本書都買不了 •事務傳播屬性可以在@Transactional註解的propagation屬性中定義
4).事務其他屬性(隔離級別&回滾&只讀&過期).
•從理論上來說, 事務應該彼此完全隔離,以避免併發事務所導致的問題.然而,那樣會對效能產生極大的影響,因為事務必須按順序執行. •在實際開發中, 為了提升效能,事務會以較低的隔離級別執行. •事務的隔離級別可以通過隔離事務屬性指定
•預設情況下只有未檢查異常(RuntimeException和Error型別的異常)會導致事務回滾.而受檢查異常不會. •事務的回滾規則可以通過 @Transactional註解的rollbackFor和noRollbackFor屬性來定義.這兩個屬性被宣告為Class[]型別的,因此可以為這兩個屬性指定多個異常類. –rollbackFor: 遇到時必須進行回滾 –noRollbackFor:一組異常類,遇到時必須不回滾 設定回滾屬性即可以用註解 ,也可以用xml配置檔案,同上
•只讀事務屬性: 表示這個事務只讀取資料但不更新資料,這樣可以幫助資料庫引擎優化事務.readOnly
•作為企業級應用程式框架, Spring 在不同的事務管理 API 之上定義了一個抽象層.而應用程式開發人員不必瞭解底層的事務管理API,就可以使用Spring的事務管理機制. •Spring 既支援程式設計式事務管理,也支援宣告式的事務管理. •程式設計式事務管理: 將事務管理程式碼嵌入到業務方法中來控制事務的提交和回滾.在程式設計式管理事務時,必須在每個事務操作中包含額外的事務管理程式碼
•Spring 從不同的事務管理API中抽象了一整套的事務機制.開發人員不必瞭解底層的事務API,就可以利用這些事務機制.有了這些事務機制, 事務管理程式碼就能獨立於特定的事務技術了. •Spring 的核心事務管理抽象是 Interface PlatFormTransactionManager
1.宣告式事務
1).用事務通知宣告式地管理事務
建表在最後面,需要注意的是本例項沒有用Hibernate或相關的框架,用的是JDBC處理事務,下面這個是基於註解 的
Dao層:
public interface BookShopDao {
//根據書號獲取書的單價
int findBookPriceByIsbn(String isbn);
//更新數的庫存. 使書號對應的庫存 - 1
void updateBookStock(String isbn);
//更新使用者的賬戶餘額: 使 username 的 balance - price
void updateUserAccount(String username, int price);
}
@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public int findBookPriceByIsbn(String isbn) {
String sql = "SELECT price FROM book WHERE isbn = ?";
return jdbcTemplate.queryForObject(sql, Integer.class, isbn);
}
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);
}
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);
}
}
定義兩個自定義異常public class BookStockException extends RuntimeException{
private static final long serialVersionUID = 1L;
public BookStockException() {
super();
}
public BookStockException(String message, Throwable cause,
boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public BookStockException(String message, Throwable cause) {
super(message, cause);
}
public BookStockException(String message) {
super(message);
}
public BookStockException(Throwable cause) {
super(cause);
}
}
public class UserAccountException extends RuntimeException{
private static final long serialVersionUID = 1L;
public UserAccountException() {
super();
}
public UserAccountException(String message, Throwable cause,
boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public UserAccountException(String message, Throwable cause) {
super(message, cause);
}
public UserAccountException(String message) {
super(message);
}
public UserAccountException(Throwable cause) {
super(cause);
}
}
Service層public interface BookShopService {
void purchase(String username, String isbn);
}
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
@Autowired
private BookShopDao bookShopDao;
//新增事務註解
//1.使用 propagation 指定事務的傳播行為, 即當前的事務方法被另外一個事務方法呼叫時
//如何使用事務, 預設取值為 REQUIRED, 即使用呼叫方法的事務
//REQUIRES_NEW: 使用自己的事務, 呼叫的事務方法的事務被掛起.
//2.使用 isolation 指定事務的隔離級別, 最常用的取值為 READ_COMMITTED
//3.預設情況下 Spring 的宣告式事務對所有的執行時異常進行回滾. 也可以通過對應的
//屬性進行設定. 通常情況下去預設值即可.
//4.使用 readOnly 指定事務是否為只讀. 表示這個事務只讀取資料但不更新資料,
//這樣可以幫助資料庫引擎優化事務. 若真的事一個只讀取資料庫值的方法, 應設定 readOnly=true
//5.使用 timeout 指定強制回滾之前事務可以佔用的時間.
// @Transactional(propagation=Propagation.REQUIRES_NEW,
// isolation=Isolation.READ_COMMITTED,
// noRollbackFor={UserAccountException.class})
@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
readOnly=false,
timeout=3)
public void purchase(String username, String isbn) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {}
//1. 獲取書的單價
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2. 更新數的庫存
bookShopDao.updateBookStock(isbn);
//3. 更新使用者餘額
bookShopDao.updateUserAccount(username, price);
}
}
public class SpringTransactionTest {
private ApplicationContext ctx = null;
private BookShopDao bookShopDao = null;
private BookShopService bookShopService = null;
private Cashier cashier = null;
{
ctx = new ClassPathXmlApplicationContext("Spring4_JDBC/applicationContext-tx.xml");
bookShopDao = ctx.getBean(BookShopDao.class);
bookShopService = ctx.getBean(BookShopService.class);
cashier = ctx.getBean(Cashier.class);
}
//
// @Test
// public void testTransactionlPropagation(){
// cashier.checkout("AA", Arrays.asList("1001", "1002"));
// }
//
@Test
public void testBookShopService(){
bookShopService.purchase("AA", "1001");
}
@Test
public void testBookShopDaoUpdateUserAccount(){
bookShopDao.updateUserAccount("AA", 200);
}
@Test
public void testBookShopDaoUpdateBookStock(){
bookShopDao.updateBookStock("1001");
}
@Test
public void testBookShopDaoFindPriceByIsbn() {
System.out.println(bookShopDao.findBookPriceByIsbn("1001"));
}
}
配置檔案<?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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="Spring4_JDBC.tx"/>
<!--匯入資原始檔-->
<context:property-placeholder location="classpath:Spring4_JDBC/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="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
<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>
<!-- 1. 配置事務管理器,管理JDBC的 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 2. 配置事務屬性 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 根據方法名指定事務的屬性 -->
<tx:method name="purchase" propagation="REQUIRES_NEW"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 3. 配置事務切入點, 以及把事務切入點和事務屬性關聯起來 -->
<aop:config>
<aop:pointcut expression="execution(* Spring4_JDBC.tx.*.*(..))"
id="txPointCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
</beans>
2).用 @Transactional註解宣告式地管理事務•除了在帶有切入點,通知和增強器的Bean配置檔案中宣告事務外,Spring 還允許簡單地用 @Transactional註解來標註事務方法. •為了將方法定義為支援事務處理的, 可以為方法新增@Transactional 註解. 根據SpringAOP 基於代理機制, 只能標註公有方法. •可以在方法或者類級別上新增 @Transactional註解.當把這個註解應用到類上時,這個類中的所有公共方法都會被定義成支援事務處理的. •在 Bean 配置檔案中只需要啟用<tx:annotation-driven>元素,併為之指定事務管理器就可以了. •如果事務處理器的名稱是 transactionManager,就可以在<tx:annotation-driven>元素中省略transaction-manager屬性.這個元素會自動檢測該名稱的事務處理器.
3).事務傳播屬性
•當事務方法被另一個事務方法呼叫時,必須指定事務應該如何傳播.例如:方法可能繼續在現有事務中執行,也可能開啟一個新事務,並在自己的事務中執行. •事務的傳播行為可以由傳播屬性指定.Spring 定義了 7 種類傳播行為. 4).Spring 支援的事務傳播行為REQUIRED 傳播行為
•當bookService的purchase()方法被另一個事務方法checkout()呼叫時,它預設會在現有的事務內執行.這個預設的傳播行為就是REQUIRED.因此在checkout()方法的開始和終止邊界內只有一個事務.這個事務只在checkout()方法結束的時候被提交,結果使用者一本書都買不了 •事務傳播屬性可以在@Transactional註解的propagation屬性中定義
REQUIRES_NEW 傳播行為
4).事務其他屬性(隔離級別&回滾&只讀&過期).
併發事務所導致的問題
•從理論上來說, 事務應該彼此完全隔離,以避免併發事務所導致的問題.然而,那樣會對效能產生極大的影響,因為事務必須按順序執行. •在實際開發中, 為了提升效能,事務會以較低的隔離級別執行. •事務的隔離級別可以通過隔離事務屬性指定
設定隔離事務屬性
•預設情況下只有未檢查異常(RuntimeException和Error型別的異常)會導致事務回滾.而受檢查異常不會. •事務的回滾規則可以通過 @Transactional註解的rollbackFor和noRollbackFor屬性來定義.這兩個屬性被宣告為Class[]型別的,因此可以為這兩個屬性指定多個異常類. –rollbackFor: 遇到時必須進行回滾 –noRollbackFor:一組異常類,遇到時必須不回滾 設定回滾屬性即可以用註解 ,也可以用xml配置檔案,同上
超時和只讀屬性
•只讀事務屬性: 表示這個事務只讀取資料但不更新資料,這樣可以幫助資料庫引擎優化事務.readOnly
CREATE TABLE book(
isbn VARCHAR(50) PRIMARY KEY,
book_name VARCHAR(100),
price INT
);
CREATE TABLE book_stock(
isbn VARCHAR(50) PRIMARY KEY,
stock INT,
CHECK(stock > 0)
);
CREATE TABLE account(
username VARCHAR(50) PRIMARY KEY,
balance INT,
CHECK(balance > 0)
);
INSERT INTO book (isbn, book_name,price) VALUES("1001","Java",100),("1002","Oracle",70);
INSERT INTO account(username,balance) VALUES("AA",160);
INSERT INTO book_stock(isbn,stock)VALUES("1001",4),("1002",8);