当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
事务的传播行为可以由传播属性指定。
Spring 定义了 7 种类传播行为:
传播属性 | 描述 | 当前不存在事务 | 当前存在事务 |
REQUIRED | 如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行 | 新建事务 | 使用当前事务 |
REQUIRES_NEW | 当前的方法必须启动新事务,并在它自己的事务内运行。如果有事务正在运行,应该将它挂起 | 新建事务 | 挂起当前事务,新建事务 |
SUPPORTS | 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中 | 不使用事务 | 使用当前事务 |
NOT_SUPPORTED | 当前的方法不应该运行在事务中,如果有运行的事务,将它挂起 | 不使用事务 | 挂起当前事务 |
MANDATORY | 当前方法必须运行在事务中,如果没有正在运行的事务,就抛出异常 | 抛出异常 | 使用当前事务 |
NEVER | 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常 | 不使用事务 | 抛出异常 |
NESTED | 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行 | 新建事务 | 嵌套事务 |
REQUIRED 传播行为:
事务传播属性可以在 @Transactional 注解的 propagation 属性中定义
@Transactional(propagation=Propagation.REQUIRED)
在 Spring2.x 事务通知中,可以在 元素中指定回滚规则。
<tx:method name="xxx" propagation="REQUIRED" />
REQUIRES_NEW 传播行为:
另一种常见的传播行为是REQUIRES_NEW。它表示该方法必须启动一个新事务,并在自己的事务内运行,如果有事务在运行,就应该先挂起它。
并发事务所导致的问题:
当同一个应用程序或者不同应用程序中的多个事务在同一个数据集上并发执行时,可能会出现许多意外的问题。
并发事务所导致的问题可以分为下面三种类型:
脏读:
一个事务读到另一个事务中没有提交的数据。
对于两个事务 T1、T2,T1 读取了已经被 T2 更新但还没有被提交的字段。之后,若 T2 回滚,T1 读取的内容就是临时且无效的。
不可重复读:
在一个事务中,两次查询同一数据,由于中间另外一个事务提交了更新,导致两次得到的结果不一致。
对于两个事务 T1、T2,T2 读取了一个字段,然后 T2 更新了该字段,之后 T1 再次读取同一个字段,值就不同了。
幻读:
一个事务执行两次查询,第二次结果集包含第一次中没有或某些行已经被删除的数据,造成两次结果不一致,只是另一个事务在这两次查询中间插入或删除了数据造成的。
对于两个事务 T1、T2,T1 从一个表中读取了一个字段,然后 T2 在该表中插入了一些新的行,之后,如果 T1 再次读取同一个表,就会多出几行。
事务的隔离级别:
从理论上来说,事务应该彼此完全隔离,以避免并发事务所导致的问题,然而,那样会对性能产生极大的影响,因为事务必须按顺序运行,在实际开发中,为了提升性能,事务会以较低的隔离级别运行。事务的隔离级别可以通过隔离事务属性指定。
Sring 支持的事务隔离级别
事务的隔离级别要得到底层数据库引擎的支持,而不是应用程序或者框架的支持。
Oracle 支持的 2 种事务隔离级别:READ_COMMITED,SERIALIZABLE
Mysql 支持 4 种事务隔离级别
隔离级别 | 描述 |
DEFAULT | 使用底层数据库的默认隔离级别,对于大多数数据库来说,默认隔离级别都是READ_COMMITED |
READ_UNCOMMITTED | 允许事务读取来被其他事务提交的变更,脏读,不可重复读和幻读的问题都会出现 |
READ_COMMITED | 只允许事务读取已经被其他事务提交的变更,可以避免脏读,但不可重复读和幻读问题仍然可能出现 |
REPEATABLE_READ | 确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重复读,但幻读的问题仍然存在。 |
SERIALIZABLE | 确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入,更新和删除操作。所有的并发问题都可以避免,但性能十分低下。 |
设置隔离事务属性
用 @Transactional 注解声明式地管理事务时可以在 @Transactional 的 isolation 属性中设置隔离级别
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITTED)
在 Spring2.x 事务通知中,可以在 元素中指定隔离级别
<tx:method name="xxx" propagation="REQUIRED"
isolation="READ_COMMITTED">
设置回滚事务属性
默认情况下只有未检查异常(RuntimeException 和 Error 类型的异常)会导致事务回滚,而受检查异常不会。
事务的回滚规则可以通过 @Transactional 注解的 rollbackFor 和 noRollbackFor 属性来定义。
这两个属性被声明为 Class[] 类型的,因此可以为这两个属性指定多个异常类
rollbackFor:遇到时必须进行回滚
noRollbackFor:一组异常类,遇到时必须不回滚
@Transactional(propagation=Propagation.REQUIRED,
isolation=Isolation.READ_COMMITTED,
rollbackFor={XXXException.class},
noRollbackFor={xxxException.class})
在 Spring2.x 事务通知中,可以在 元素中指定回滚规则,如果有不止一种异常,用逗号分隔。
<tx:method name="xxx"
propagation="REQUIRED",
isolation="READ_COMMITTED",
rollback-for="java.io.IOException,java.sql.SQLException",
no-rollback-for="java.lang.ArithmeticException/">
延时和只读属性
由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。
如果一个事务只读取数据但不做修改,数据库引擎可以对这个事务进行优化。
超时事务属性:事务在强制回滚之前可以保持多久,这样可以防止长期运行的事务占用资源。
只读事务属性:表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务。
设置超时和只读事务属性:
超时和只读事务可以在 @Transactional 注解中定义,超时属性以秒为单位来计算
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITTED,
rollbackFor={XXXException.class},timeout=30,read-only=true)
在 Spring2.x 事务通知中,超时和只读属性可以在 元素中进行指定
<tx:method name="xxx"
propagation="REQUIRED"
isolation="READ_COMMITTED",
rollback-for="java.io.IOException,java.sql.SQLException"
no-rollback-for="java.lang.ArithmeticException
timeout="30"
read-only="true"/">
现在开始上代码部分,代码的结构与内容都与前面的文章一样,
上一篇文章地址:
https://blog.youkuaiyun.com/qq_33811662/article/details/80560897
现在说明下修改的地方:
事务传播属性示例:
REQUIRED
在 OrderService 中的 insertOrder 代码为:
@Transactional(propagation=Propagation.REQUIRED)
public void insertOrder(String customerName, String bookName, int orderQuantities) {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setCustomerName(customerName);
orderInfo.setBookName(bookName);
orderInfo.setOrderQuantities(orderQuantities);
double price = bookService.getPriceByName(bookName);
double payCount = orderQuantities * price;
orderInfo.setPayCount(payCount);
orderDao.insertOrder(orderInfo);
bookService.updateBookStock(bookName, orderQuantities);
customerService.updateCustomer(customerName, payCount);
}
在 BookService 中的代码为:
@Transactional(propagation = Propagation.REQUIRED)
public int updateBookStock(String name, int stock) {
return bookDao.updateBookStock(name, stock);
}
此时两个注解的 propagation 传播行为都是 REQUIRED,
运行程序输出结果为:
springtx.exception.AccountException: 金钱不足,购买失败!
BookInfo [id=1, name=Java从入门到放弃, price=15.0, stock=100]
BookInfo [id=2, name=Mysql从删库到跑路, price=22.5, stock=120]
BookInfo [id=3, name=Linux从入门到砸电脑, price=17.0, stock=15]
CustomerInfo [id=1, name=小明, account=40.0]
CustomerInfo [id=2, name=大明, account=70.0]
REQUIRES_NEW
OrderService 事务代码:
@Transactional(propagation=Propagation.REQUIRED)
public void insertOrder(String customerName, String bookName, int orderQuantities) {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setCustomerName(customerName);
orderInfo.setBookName(bookName);
orderInfo.setOrderQuantities(orderQuantities);
double price = bookService.getPriceByName(bookName);
double payCount = orderQuantities * price;
orderInfo.setPayCount(payCount);
orderDao.insertOrder(orderInfo);
bookService.updateBookStock(bookName, orderQuantities);
customerService.updateCustomer(customerName, payCount);
}
BookService 事务代码:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int updateBookStock(String name, int stock) {
return bookDao.updateBookStock(name, stock);
}
运行输出结果:
springtx.exception.AccountException: 金钱不足,购买失败!
BookInfo [id=1, name=Java从入门到放弃, price=15.0, stock=85]
BookInfo [id=2, name=Mysql从删库到跑路, price=22.5, stock=120]
BookInfo [id=3, name=Linux从入门到砸电脑, price=17.0, stock=15]
CustomerInfo [id=1, name=小明, account=40.0]
CustomerInfo [id=2, name=大明, account=70.0]
此时虽然 order 表数据正常回滚,但是 book 表却出现了问题,图书少了 15 本,REQUIRES_NEW 新开了一个事务,所以 OrderService 回滚的时候,并没有带上 BookService。
SUPPORTS
此时数据库已经被改回初始状态:
OrderService 事务代码:
@Transactional(propagation=Propagation.REQUIRED)
public void insertOrder(String customerName, String bookName, int orderQuantities) {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setCustomerName(customerName);
orderInfo.setBookName(bookName);
orderInfo.setOrderQuantities(orderQuantities);
double price = bookService.getPriceByName(bookName);
double payCount = orderQuantities * price;
orderInfo.setPayCount(payCount);
orderDao.insertOrder(orderInfo);
bookService.updateBookStock(bookName, orderQuantities);
customerService.updateCustomer(customerName, payCount);
}
BookService 事务代码:
@Transactional(propagation = Propagation.SUPPORTS)
public int updateBookStock(String name, int stock) {
return bookDao.updateBookStock(name, stock);
}
运行程序结果:
springtx.exception.AccountException: 金钱不足,购买失败!
BookInfo [id=1, name=Java从入门到放弃, price=15.0, stock=100]
BookInfo [id=2, name=Mysql从删库到跑路, price=22.5, stock=120]
BookInfo [id=3, name=Linux从入门到砸电脑, price=17.0, stock=15]
CustomerInfo [id=1, name=小明, account=40.0]
CustomerInfo [id=2, name=大明, account=70.0]
此时 BookService 与 OrderService 为同一个事务,那么看下另外一种情况,就是当前没有事务的情况。
OrderService 事务代码:
@Transactional(propagation=Propagation.SUPPORTS)
public void insertOrder(String customerName, String bookName, int orderQuantities) {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setCustomerName(customerName);
orderInfo.setBookName(bookName);
orderInfo.setOrderQuantities(orderQuantities);
double price = bookService.getPriceByName(bookName);
double payCount = orderQuantities * price;
orderInfo.setPayCount(payCount);
orderDao.insertOrder(orderInfo);
bookService.updateBookStock(bookName, orderQuantities);
customerService.updateCustomer(customerName, payCount);
}
BookService 事务代码:
public int updateBookStock(String name, int stock) {
return bookDao.updateBookStock(name, stock);
}
运行代码输出结果:
springtx.exception.AccountException: 金钱不足,购买失败!
BookInfo [id=1, name=Java从入门到放弃, price=15.0, stock=85]
BookInfo [id=2, name=Mysql从删库到跑路, price=22.5, stock=120]
BookInfo [id=3, name=Linux从入门到砸电脑, price=17.0, stock=15]
CustomerInfo [id=1, name=小明, account=40.0]
CustomerInfo [id=2, name=大明, account=70.0]
OrderInfo [id=55, customerName=大明, bookName=Java从入门到放弃, orderQuantities=15, payCount=225.0]
此时程序中实际上没有任何事务,所以回滚全部失败!!实际上 SUPPORT 也不常用,再次还原数据库。
NOT_SUPPORTED
OrderService 事务代码:
@Transactional(propagation=Propagation.NOT_SUPPORTED)
public void insertOrder(String customerName, String bookName, int orderQuantities) {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setCustomerName(customerName);
orderInfo.setBookName(bookName);
orderInfo.setOrderQuantities(orderQuantities);
double price = bookService.getPriceByName(bookName);
double payCount = orderQuantities * price;
orderInfo.setPayCount(payCount);
orderDao.insertOrder(orderInfo);
bookService.updateBookStock(bookName, orderQuantities);
customerService.updateCustomer(customerName, payCount);
}
BookService 事务代码:
public int updateBookStock(String name, int stock) {
return bookDao.updateBookStock(name, stock);
}
输出结果:
springtx.exception.AccountException: 金钱不足,购买失败!
BookInfo [id=1, name=Java从入门到放弃, price=15.0, stock=85]
BookInfo [id=2, name=Mysql从删库到跑路, price=22.5, stock=120]
BookInfo [id=3, name=Linux从入门到砸电脑, price=17.0, stock=15]
CustomerInfo [id=1, name=小明, account=40.0]
CustomerInfo [id=2, name=大明, account=70.0]
OrderInfo [id=56, customerName=大明, bookName=Java从入门到放弃, orderQuantities=15, payCount=225.0]
此时代码中没有任何事务。
还原数据库,加上一个事务:
OrderService 事务代码:
@Transactional(propagation = Propagation.REQUIRED)
public void insertOrder(String customerName, String bookName, int orderQuantities) {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setCustomerName(customerName);
orderInfo.setBookName(bookName);
orderInfo.setOrderQuantities(orderQuantities);
double price = bookService.getPriceByName(bookName);
double payCount = orderQuantities * price;
orderInfo.setPayCount(payCount);
orderDao.insertOrder(orderInfo);
bookService.updateBookStock(bookName, orderQuantities);
customerService.updateCustomer(customerName, payCount);
}
BookService 事务代码:
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public int updateBookStock(String name, int stock) {
return bookDao.updateBookStock(name, stock);
}
输出如下结果:
springtx.exception.AccountException: 金钱不足,购买失败!
BookInfo [id=1, name=Java从入门到放弃, price=15.0, stock=85]
BookInfo [id=2, name=Mysql从删库到跑路, price=22.5, stock=120]
BookInfo [id=3, name=Linux从入门到砸电脑, price=17.0, stock=15]
CustomerInfo [id=1, name=小明, account=40.0]
CustomerInfo [id=2, name=大明, account=70.0]
实际上在 BookService 中,将当前事务挂起了,所以 BookService 所操作的数据并没有回滚,数据库再次恢复原始数据。
MANDATORY
OrderService 事务代码:
@Transactional(propagation = Propagation.REQUIRED)
public void insertOrder(String customerName, String bookName, int orderQuantities) {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setCustomerName(customerName);
orderInfo.setBookName(bookName);
orderInfo.setOrderQuantities(orderQuantities);
double price = bookService.getPriceByName(bookName);
double payCount = orderQuantities * price;
orderInfo.setPayCount(payCount);
orderDao.insertOrder(orderInfo);
bookService.updateBookStock(bookName, orderQuantities);
customerService.updateCustomer(customerName, payCount);
}
BookService 事务代码:
@Transactional(propagation = Propagation.MANDATORY)
public int updateBookStock(String name, int stock) {
return bookDao.updateBookStock(name, stock);
}
运行结果为:
springtx.exception.AccountException: 金钱不足,购买失败!
BookInfo [id=1, name=Java从入门到放弃, price=15.0, stock=100]
BookInfo [id=2, name=Mysql从删库到跑路, price=22.5, stock=120]
BookInfo [id=3, name=Linux从入门到砸电脑, price=17.0, stock=15]
CustomerInfo [id=1, name=小明, account=40.0]
CustomerInfo [id=2, name=大明, account=70.0]
此时 BookService 使用了 OrderService 所带的事务,所以数据回滚正常。但如果当前没有事务的话:将 OrderService 的注解注释掉
运行程序:
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
BookInfo [id=1, name=Java从入门到放弃, price=15.0, stock=100]
BookInfo [id=2, name=Mysql从删库到跑路, price=22.5, stock=120]
BookInfo [id=3, name=Linux从入门到砸电脑, price=17.0, stock=15]
CustomerInfo [id=1, name=小明, account=40.0]
CustomerInfo [id=2, name=大明, account=70.0]
OrderInfo [id=59, customerName=大明, bookName=Java从入门到放弃, orderQuantities=15, payCount=225.0]
抛出了 IllegalTransactionStateException,此时压根没有到 BookService 的业务代码中执行就直接抛出了错误!!
NEVER
OrderService 事务代码:
@Transactional(propagation = Propagation.REQUIRED)
public void insertOrder(String customerName, String bookName, int orderQuantities) {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setCustomerName(customerName);
orderInfo.setBookName(bookName);
orderInfo.setOrderQuantities(orderQuantities);
double price = bookService.getPriceByName(bookName);
double payCount = orderQuantities * price;
orderInfo.setPayCount(payCount);
orderDao.insertOrder(orderInfo);
bookService.updateBookStock(bookName, orderQuantities);
customerService.updateCustomer(customerName, payCount);
}
BookService 事务代码:
@Transactional(propagation = Propagation.NEVER)
public int updateBookStock(String name, int stock) {
return bookDao.updateBookStock(name, stock);
}
运行结果:
org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
BookInfo [id=1, name=Java从入门到放弃, price=15.0, stock=100]
BookInfo [id=2, name=Mysql从删库到跑路, price=22.5, stock=120]
BookInfo [id=3, name=Linux从入门到砸电脑, price=17.0, stock=15]
CustomerInfo [id=1, name=小明, account=40.0]
CustomerInfo [id=2, name=大明, account=70.0]
此时直接抛出异常,实际上此时压根没进入 BookService 的 update 方法就抛出异常使得 OrderService 进行回滚。
如果未存在事务的话:
OrderService 事务代码:
@Transactional(propagation = Propagation.NEVER)
public void insertOrder(String customerName, String bookName, int orderQuantities) {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setCustomerName(customerName);
orderInfo.setBookName(bookName);
orderInfo.setOrderQuantities(orderQuantities);
double price = bookService.getPriceByName(bookName);
double payCount = orderQuantities * price;
orderInfo.setPayCount(payCount);
orderDao.insertOrder(orderInfo);
bookService.updateBookStock(bookName, orderQuantities);
customerService.updateCustomer(customerName, payCount);
}
BookService 事务代码:
public int updateBookStock(String name, int stock) {
return bookDao.updateBookStock(name, stock);
}
执行结果:
springtx.exception.AccountException: 金钱不足,购买失败!
BookInfo [id=1, name=Java从入门到放弃, price=15.0, stock=85]
BookInfo [id=2, name=Mysql从删库到跑路, price=22.5, stock=120]
BookInfo [id=3, name=Linux从入门到砸电脑, price=17.0, stock=15]
CustomerInfo [id=1, name=小明, account=40.0]
CustomerInfo [id=2, name=大明, account=70.0]
OrderInfo [id=63, customerName=大明, bookName=Java从入门到放弃, orderQuantities=15, payCount=225.0]
代码中无事务,不会报系统异常,但是也不会回滚,还是需要手动重置下数据库内容。
NESTED
OrderService 事务代码:
@Transactional(propagation = Propagation.REQUIRED)
public void insertOrder(String customerName, String bookName, int orderQuantities) {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setCustomerName(customerName);
orderInfo.setBookName(bookName);
orderInfo.setOrderQuantities(orderQuantities);
double price = bookService.getPriceByName(bookName);
double payCount = orderQuantities * price;
orderInfo.setPayCount(payCount);
orderDao.insertOrder(orderInfo);
bookService.updateBookStock(bookName, orderQuantities);
customerService.updateCustomer(customerName, payCount);
}
BookService 事务代码:
@Transactional(propagation = Propagation.NESTED)
public int updateBookStock(String name, int stock) {
return bookDao.updateBookStock(name, stock);
}
执行结果:
springtx.exception.AccountException: 金钱不足,购买失败!
BookInfo [id=1, name=Java从入门到放弃, price=15.0, stock=100]
BookInfo [id=2, name=Mysql从删库到跑路, price=22.5, stock=120]
BookInfo [id=3, name=Linux从入门到砸电脑, price=17.0, stock=15]
CustomerInfo [id=1, name=小明, account=40.0]
CustomerInfo [id=2, name=大明, account=70.0]
此时嵌套事务,回滚正常。
如果代码中不存在其他事务:
OrderService 事务代码:
@Transactional(propagation = Propagation.NESTED)
public void insertOrder(String customerName, String bookName, int orderQuantities) {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setCustomerName(customerName);
orderInfo.setBookName(bookName);
orderInfo.setOrderQuantities(orderQuantities);
double price = bookService.getPriceByName(bookName);
double payCount = orderQuantities * price;
orderInfo.setPayCount(payCount);
orderDao.insertOrder(orderInfo);
bookService.updateBookStock(bookName, orderQuantities);
customerService.updateCustomer(customerName, payCount);
}
BookService 事务代码:
public int updateBookStock(String name, int stock) {
return bookDao.updateBookStock(name, stock);
}
执行结果:
springtx.exception.AccountException: 金钱不足,购买失败!
BookInfo [id=1, name=Java从入门到放弃, price=15.0, stock=100]
BookInfo [id=2, name=Mysql从删库到跑路, price=22.5, stock=120]
BookInfo [id=3, name=Linux从入门到砸电脑, price=17.0, stock=15]
CustomerInfo [id=1, name=小明, account=40.0]
CustomerInfo [id=2, name=大明, account=70.0]
此时新建了事务,不过 NESTED 在 3 个事务以上才能看得出效果,此处不明显。
设置回滚事务属性
OrderService 中事务代码:
@Transactional(propagation = Propagation.REQUIRED, noRollbackFor = AccountException.class)
public void insertOrder(String customerName, String bookName, int orderQuantities) {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setCustomerName(customerName);
orderInfo.setBookName(bookName);
orderInfo.setOrderQuantities(orderQuantities);
double price = bookService.getPriceByName(bookName);
double payCount = orderQuantities * price;
orderInfo.setPayCount(payCount);
orderDao.insertOrder(orderInfo);
bookService.updateBookStock(bookName, orderQuantities);
customerService.updateCustomer(customerName, payCount);
}
此处指定了AccountException 不会回滚,那么执行代码:
springtx.exception.AccountException: 金钱不足,购买失败!
BookInfo [id=1, name=Java从入门到放弃, price=15.0, stock=85]
BookInfo [id=2, name=Mysql从删库到跑路, price=22.5, stock=120]
BookInfo [id=3, name=Linux从入门到砸电脑, price=17.0, stock=15]
CustomerInfo [id=1, name=小明, account=40.0]
CustomerInfo [id=2, name=大明, account=70.0]
OrderInfo [id=67, customerName=大明, bookName=Java从入门到放弃, orderQuantities=15, payCount=225.0]
即使报错,也不控制回滚!