事务传播属性
当事务方法被另一个事务方法调用时, 必须指定事务应该如何传播. 例如: 方法可能继续在现有事务中运行, 也可能开启一个新事务, 并在自己的事务中运行.
事务的传播行为可以由传播属性指定. Spring 定义了 7 种类传播行为.
Spring 支持的事务传播行为
需求
新定义 Cashier 接口: 表示客户的结账操作
修改数据表信息如下, 目的是用户 Tom 在结账时, 余额只能支付第一本书, 不够支付第二本书:
REQUIRED 传播行为
当 bookService 的 purchase() 方法被另一个事务方法 checkout() 调用时, 它默认会在现有的事务内运行. 这个默认的传播行为就是 REQUIRED. 因此在 checkout() 方法的开始和终止边界内只有一个事务. 这个事务只在 checkout() 方法结束的时候被提交, 结果用户一本书都买不了
事务传播属性可以在 @Transactional 注解的 propagation 属性中定义
REQUIRES_NEW 传播行为
另一种常见的传播行为是 REQUIRES_NEW. 它表示该方法必须启动一个新事务, 并在自己的事务内运行. 如果有事务在运行, 就应该先挂起它.
在 Spring 2.x 事务通知中配置传播属性
在 Spring 2.x 事务通知中, 可以像下面这样在 <tx:method> 元素中设定传播事务属性
并发事务所导致的问题
当同一个应用程序或者不同应用程序中的多个事务在同一个数据集上并发执行时, 可能会出现许多意外的问题
并发事务所导致的问题可以分为下面三种类型:
– 脏读: 对于两个事物 T1, T2, T1 读取了已经被 T2 更新但 还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的.
– 不可重复读:对于两个事物 T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了.
– 幻读:对于两个事物 T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行. 之后, 如果 T1 再次读取同一个表, 就会多出几行.
事务的隔离级别
从理论上来说, 事务应该彼此完全隔离, 以避免并发事务所导致的问题. 然而, 那样会对性能产生极大的影响, 因为事务必须按顺序运行.
在实际开发中, 为了提升性能, 事务会以较低的隔离级别运行.
事务的隔离级别可以通过隔离事务属性指定
Spring 支持的事务隔离级别
事务的隔离级别要得到底层数据库引擎的支持, 而不是应用程序或者框架的支持.
Oracle 支持的 2 种事务隔离级别:READ_COMMITED , SERIALIZABLE
Mysql 支持 4 中事务隔离级别.
设置隔离事务属性
用 @Transactional 注解声明式地管理事务时可以在 @Transactional 的 isolation 属性中设置隔离级别.
在 Spring 2.x 事务通知中, 可以在 <tx:method> 元素中指定隔离级别
设置回滚事务属性
默认情况下只有未检查异常(RuntimeException和Error类型的异常)会导致事务回滚. 而受检查异常不会.
事务的回滚规则可以通过 @Transactional 注解的 rollbackFor 和 noRollbackFor 属性来定义. 这两个属性被声明为 Class[] 类型的, 因此可以为这两个属性指定多个异常类.
rollbackFor: 遇到时必须进行回滚
noRollbackFor: 一组异常类,遇到时必须不回滚
设置回滚事务属性
在 Spring 2.x 事务通知中, 可以在 <tx:method> 元素中指定回滚规则. 如果有不止一种异常, 用逗号分隔.
超时和只读属性
由于事务可以在行和表上获得锁, 因此长事务会占用资源, 并对整体性能产生影响.
如果一个事物只读取数据但不做修改, 数据库引擎可以对这个事务进行优化.
超时事务属性: 事务在强制回滚之前可以保持多久. 这样可以防止长期运行的事务占用资源.
只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务.
设置超时和只读事务属性
超时和只读属性可以在 @Transactional 注解中定义.超时属性以秒为单位来计算.
在 Spring 2.x 事务通知中, 超时和只读属性可以在 <tx:method> 元素中进行指定.
package com.learn.spring.tx.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class CashierImpl implements Cashier{
@Autowired
private BookShopService bookShopService;
@Override
@Transactional
public void checkOut(String username, List<String> isbns) {
for (String isbn : isbns) {
bookShopService.buyBook(username, isbn);
}
}
}
package com.learn.spring.tx.service;
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;
import com.learn.spring.tx.dao.BookShopDao;
import com.learn.spring.tx.exception.UserAccountException;
@Service
//@Transactional //对该类中所有的方法都起作用
public class BookShopServiceImpl implements BookShopService{
@Autowired
private BookShopDao bookShopDao ;
/**
* 事务的属性:
* propagation:事务的传播行为.
* REQUIRED:使用调用者的事务
* REQUIRES_NEW:将调用者的事务挂起,使用自己的新事务.
*
* isolation:事务的隔离级别,最常用的就是READ_COMMITTED
* readOnly:指定事务是否为只读. 如果是只读事务,代表这个事务只读取数据库的数据.而不进行修改操作 .
* 若一个事务真的是只读取数据,就有必须要设置readOnly=true,可以帮助数据库引擎进行优化
*
* rollbackFor
* rollbackForClassName
* noRollbackFor
* noRollbackForClassName
*
* timeout:指定强制回滚前事务可以占用的时间。 为了避免一个事务占用过长的时间.
*
*/
@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
readOnly=false
/*noRollbackFor={UserAccountException.class}*/
/*timeout=3*/)
public void buyBook(String username, String isbn) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//1.查询书的价格
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2.更新书的库存
bookShopDao.updateBookStock(isbn);
//3.更新用户的余额
bookShopDao.updateUserAccount(username, price);
}
//
}