Spring 实现事务管理

编程式事务管理:

将事务管理代码嵌入到业务方法中来控制事务的提交和回滚,在编程式管理事务中,必须在每个事务操作中包含额外的事务管理代码。https://blog.csdn.net/Zzze0101/article/details/90176413

声明式事务管理:

大多情况下比编程式事务管理更加好用,此方式将事务管理代码从业务代码中分离出来,以声明的方式实现事务管理,Spring声明式事务管理建立在AOP基础之上,是一个典型的横切关注点,通过环绕增强来实现,其原理是对方法前后进行拦截,然后在目标方法开始之前创建或加入一个事务,在执行完毕之后根据执行情况提交或回滚事务。
通过一个订单管理模块来说明Spring实现事务管理:

  1. 除Spring所需jar包之外还需要spring-aspects-4.2.5.RELEASE.jar
  2. 在Spring配置文件中添加如下配置:
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
  1. 在Service层public方法上添加事务注解——@Transactional
    (1)、一个类含有@Transactional注解修饰的方法,则Spring框架自动为该类创建代理对象,默认使用JDK创建代理对象。
    (2)、不能在protected、默认或者private的方法上使用@Transactional注解,否则无效。

@Transactional注解属性:

此文章只介绍@Transactional注解,不介绍代码内部方法如何实现

  1. rollbackFor和rollbackForClassName:指定对哪些异常回滚事务。
    默认情况下,如果在事务中抛出了运行时异常(继承自RuntimeException异常类),则回滚事务;如果没有抛出任何异常,或者抛出了检查时异常,则依然提交事务。这种处理方式是大多数开发者希望的处理方式,也是 EJB 中的默认处理方式;但可以根据需要人为控制事务在抛出某些运行时异常时仍然提交事务,或者在抛出某些检查时异常时回滚事务。
@Transactional(rollbackFor = MoneyException.class)//MoneyException为自定义运行时异常
public boolean insert(String userId, String bookId, int count){//添加订单
	int i = bookDao.enough(bookId, count);//判断书籍库存
	if(i>=0) {
		bookDao.update(bookId, i);
	}else {
		throw new BookException("书籍库存不足!");//库存不足抛出自定义书籍异常
	}
	double price = bookDao.select(bookId).getPrice();//获取价格
	double total = price*count;//计算总价格
	if(moneyDao.enough(userId, total)) {//判断钱包余额是否足够
		Coupon coupon = new Coupon(userId, bookId, total);//新建订单
		couponDao.insert(coupon);//添加订单
		moneyDao.update(userId, total);//更新数据
	}else {
		throw new MoneyException("余额不足!");//余额不足抛出自定义余额异常
	}
return true;
}
执行结果:
若书籍库存不足则抛出异常,此时并没有进行DML语言,数据不变
若余额不足则抛出异常MoneyException,此时发生事务回滚,数据库中数据不会改变,
  1. noRollbackFor和noRollbackForClassName:指定对哪些异常不回滚事务。
    此属性在实际中一般不用,在此不在叙述。
  2. readOnly:事务只读,指对事务性资源进行只读操作。
    所谓事务性资源就是指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么可以将事务标志为只读的,以提高事务处理的性能。
@Transactional(readOnly=true)//readOnly默认是false
public boolean insert(String userId, String bookId, int count){//添加订单
	int i = bookDao.enough(bookId, count);//判断书籍库存
	if(i>=0) {
		bookDao.update(bookId, i);
	}else {
		throw new BookException("书籍库存不足!");//库存不足抛出自定义书籍异常
	}
	double price = bookDao.select(bookId).getPrice();//获取价格
	double total = price*count;//计算总价格
	if(moneyDao.enough(userId, total)) {//判断钱包余额是否足够
		Coupon coupon = new Coupon(userId, bookId, total);//新建订单
		couponDao.insert(coupon);//添加订单
		moneyDao.update(userId, total);//更新数据
	}else {
		throw new MoneyException("余额不足!");//余额不足抛出自定义余额异常
	}
return true;
}
执行结果:出现异常
SQL [update book set quantity = ? where id=?]; Connection is read-only. 
Queries leading to data modification are not allowed; nested exception is java.sql.
SQLException: Connection is read-only. 
Queries leading to data modification are not allowed
  1. timeout:设置一个事务所允许执行的最长时长(单位:秒)。
    如果超过该时长且事务还没有完成,则自动回滚事务且出现org.springframework.transaction.TransactionTimedOutException异常。
@Transactional(timeout=3)//readOnly默认是false
public boolean insert(String userId, String bookId, int count){//添加订单
	int i = bookDao.enough(bookId, count);//判断书籍库存
	if(i>=0) {
		bookDao.update(bookId, i);
	}else {
		throw new BookException("书籍库存不足!");//库存不足抛出自定义书籍异常
	}
	try {
		Thread.sleep(4000);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	double price = bookDao.select(bookId).getPrice();//获取价格
	double total = price*count;//计算总价格
	if(moneyDao.enough(userId, total)) {//判断钱包余额是否足够
		Coupon coupon = new Coupon(userId, bookId, total);//新建订单
		couponDao.insert(coupon);//添加订单
		moneyDao.update(userId, total);//更新数据
	}else {
		throw new MoneyException("余额不足!");//余额不足抛出自定义余额异常
	}
return true;
}
执行结果:抛出异常
org.springframework.transaction.TransactionTimedOutException:
Transaction timed out: deadline was Sun Jul 14 19:41:14 CST 2019
事务回滚,数据未改变

注意:

  • 事务的开始往往会发生数据库的表锁或者被数据库优化为行锁,如果允许时间过长,那么这些数据会一直被锁定,最终影响系统的并发性,因此可以给这些事务设置超时时间以规避该问题。
  • 由于超时是在一个事务启动的时候开始的,因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEW、PROPAGATION_REQUIRED、ROPAGATION_NESTED)的方法来说,声明事务超时才有意义。
  1. propagation:指定事务传播行为.
    一个事务方法被另一个事务方法调用时,必须指定事务应该如何传播,例如:方法可能继承在现有事务中运行,也可能开启一个新事物,并在自己的事务中运行。
    Spring定义了如下7种事务传播行为:
  • REQUIRED:默认值,如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行;

场景:购物车模块,用户选择了“活着”,“朝花夕拾”两本书,两本书的价钱都为10元,但是用户余额只有10元,接着选择购买:

//注意下面两个方法在不同Service中,在之后会讲述区别
@Transactional
public boolean batch(String userId, Map<String, Integer> bookMap) {//实现购物车批量购买
	Set<Entry<String,Integer>> set = bookMap.entrySet();
	for (Entry<String, Integer> entry : set) {
		String bookName = entry.getKey();
		int bookNumber = entry.getValue();
		CouponService.insert(userId, bookName, bookNumber);//调用另一个Service中的insert方法
	}
return false;
}
@Transactional(propagation=Propagation.REQUIRED)//
public boolean insert(String userId, String bookId, int count){//添加订单
	int i = bookDao.enough(bookId, count);//判断书籍库存
	if(i>=0) {
		bookDao.update(bookId, i);
	}else {
		throw new BookException("书籍库存不足!");//库存不足抛出自定义书籍异常
	}
	try {
		Thread.sleep(4000);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	double price = bookDao.select(bookId).getPrice();//获取价格
	double total = price*count;//计算总价格
	if(moneyDao.enough(userId, total)) {//判断钱包余额是否足够
		Coupon coupon = new Coupon(userId, bookId, total);//新建订单
		couponDao.insert(coupon);//添加订单
		moneyDao.update(userId, total);//更新数据
	}else {
		throw new MoneyException("余额不足!");//余额不足抛出自定义余额异常
	}
return true;
}
执行结果:
余额不足!且数据库中书籍表,订单表,用户余额表均没有发生改变。虽然用户余额足够买一本书,但是,
对于两本书的购买处于同一个事务中,所以同时成功,同时失败。
  • REQUIRES_NEW:当前方法必须启动新事务,并在它自己的事务内运行,如果有事务在运行,则把当前事务挂起,直到新的事务提交或者回滚才恢复执行;
//注意下面两个方法在不同Service中,在之后会讲述区别
@Transactional
public boolean batch(String userId, Map<String, Integer> bookMap) {//实现购物车批量购买
	Set<Entry<String,Integer>> set = bookMap.entrySet();
	for (Entry<String, Integer> entry : set) {
		String bookName = entry.getKey();
		int bookNumber = entry.getValue();
		CouponService.insert(userId, bookName, bookNumber);//调用另一个Service中的insert方法
	}
return false;
}
@Transactional(propagation=Propagation.REQUIRES_NEW)//
public boolean insert(String userId, String bookId, int count){//添加订单
	int i = bookDao.enough(bookId, count);//判断书籍库存
	if(i>=0) {
		bookDao.update(bookId, i);
	}else {
		throw new BookException("书籍库存不足!");//库存不足抛出自定义书籍异常
	}
	try {
		Thread.sleep(4000);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	double price = bookDao.select(bookId).getPrice();//获取价格
	double total = price*count;//计算总价格
	if(moneyDao.enough(userId, total)) {//判断钱包余额是否足够
		Coupon coupon = new Coupon(userId, bookId, total);//新建订单
		couponDao.insert(coupon);//添加订单
		moneyDao.update(userId, total);//更新数据
	}else {
		throw new MoneyException("余额不足!");//余额不足抛出自定义余额异常
	}
return true;
}

还原场景之后再次执行(因为数据没有变化,所以不用还原):

执行结果:
数据库中有一本书的数据发生变化,并且订单表有了一条新数据,说明购物车中一本书购买成功,
一本书购买失败,存在两个事务。

注意下面两个方法在同一个Service中

@Transactional
public boolean batch(String userId, Map<String, Integer> bookMap) {//实现购物车批量购买
	Set<Entry<String,Integer>> set = bookMap.entrySet();
	for (Entry<String, Integer> entry : set) {
		String bookName = entry.getKey();
		int bookNumber = entry.getValue();
		insert(userId, bookName, bookNumber);//调用另一个Service中的insert方法
	}
return false;
}
@Transactional(propagation=Propagation.REQUIRES_NEW)//
public boolean insert(String userId, String bookId, int count){//添加订单
	int i = bookDao.enough(bookId, count);//判断书籍库存
	if(i>=0) {
		bookDao.update(bookId, i);
	}else {
		throw new BookException("书籍库存不足!");//库存不足抛出自定义书籍异常
	}
	try {
		Thread.sleep(4000);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	double price = bookDao.select(bookId).getPrice();//获取价格
	double total = price*count;//计算总价格
	if(moneyDao.enough(userId, total)) {//判断钱包余额是否足够
		Coupon coupon = new Coupon(userId, bookId, total);//新建订单
		couponDao.insert(coupon);//添加订单
		moneyDao.update(userId, total);//更新数据
	}else {
		throw new MoneyException("余额不足!");//余额不足抛出自定义余额异常
	}
return true;
}

还原场景之后再次执行:

执行结果:
余额不足!且数据库中书籍表,订单表,用户余额表均没有发生改变。虽然用户余额足够买一本书,并且
propagation=Propagation.REQUIRES_NEW,但是在同一个Service中这两个方法实践上还是在同一个事务中,
所以对于两本书的购买同时成功,同时失败。
  • SUPPORTS:如果有事务在运行,当前的方法就在这个事务内运行,否则以非事务的方式运行;
  • NOT_SUPPORTED:当前的方法不应该运行在事务中,如果有运行的事务,则将它挂起;
  • NEVER:当前方法不应该运行在事务中,否则将抛出异常;
  • MANDATORY:当前方法必须运行在事务内部,否则将抛出异常;
  • NESTED:如果有事务在运行,当前的方法在这个事务的嵌套事务内运行,否则就启动一个新的事务,并在它自己的事务内运行,此时等价于REQUIRED。

注意:对于NESTED内层事务而言,内层事务独立于外层事务,可以独立递交或者回滚,如果内层事务抛出的是运行异常,外层事务进行回滚,内层事务也会进行回滚。

  1. isolation:指定事务隔离级别,Spring定义了5种事务隔离级别。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值