欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring框架事务 @Transactional常用属性说明

程序员文章站 2022-07-12 13:18:01
...
一、什么是Spring事务?

1、Spring事务满足事务四大特性(ACID):

原子性(Atomicity) 数据库中原子性强调事务是一个不可分割的整体,事务开始后所有操作要么全部成功,要么全部失败,不可能停滞在中间某个环节。如果事务执行过程中出错就会回滚到事务开始前的状态,所有的操作就像没有发生一样不会对数据库有任何影响。
一致性(Consistency) 事务必须使数据库从一个一致性状态变换到另一个一致性状态,即一个事务执行之前和执行之后都必须处于一致性状态。拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还是5000,这就是事务的一致性。
隔离性(Isolation) 当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离,比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转入钱。
持久性(Durability) 一个事务一旦被提交,则对数据库的所有更新将被保存到数据库中,不能回滚。

2、Spring事务的逻辑单元由java代码构成,一个事务内的java代码全部执行成功或者全部不执行。

二、如何配置事务?

Spring 实现事务管理有如下两种方式:

1、编程式事务管理:
将事务管理代码嵌入到业务方法中来控制事务的提交和回滚,在编程式管理事务中,必须在每个事务操作中包含额外的事务管理代码。

2、声明式事务管理:
大多数情况下比编程式事务管理更好用,它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理,Spring声明式事务管理建立在AOP基础之上,是一个典型的横切关注点,通过环绕增强来实现,其原理是对方法前后进行拦截,然后在目标方法开始之前创建或加入一个事务,在执行完毕之后根据执行情况提交或回滚事务。实现声明式事务步骤如下:
①添加spring-aspects-4.3.10.RELEASE.jar包:
Spring框架事务 @Transactional常用属性说明
②设置Spring配置文件:

<?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:aop="http://www.springframework.org/schema/aop"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:util="http://www.springframework.org/schema/util"
	xmlns:tx="http://www.springframework.org/schema/tx"
	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-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">
	<context:component-scan base-package="com.jd"></context:component-scan>
	
	<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
		<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
		<property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8"></property>
		<property name="username" value="root"></property>
		<property name="password" value="root"></property>
	</bean>

	<bean class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- 配置数据源事务管理器 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource"></bean>
	
	<!-- 启用事务注解 -->
	<tx:annotation-driven proxy-target-class="true"/>
</beans>

③在Service层public方法上添加事务注解——@Transactional:

@Service
public class CouponService implements ICouponService {

	@Autowired
	private IBookDao bookDao;
	@Autowired
	private IMoneyDao moneyDao;
	@Autowired
	private ICouponDao couponDao;
	
	@Transactional
	public boolean insert(String userId,String bookId, int count){
		
		if(bookDao.enough(bookId, count)) {//书籍足够
			//书籍表库存递减
			bookDao.update(bookId, count);
		}
		double price = bookDao.getPrice(bookId);
		double total = price*count;
		if(moneyDao.enough(userId, total)) {//余额足够
			//订单表添加数据
			Coupon coupon = new Coupon();
			coupon.setId(UUID.randomUUID().toString());
			coupon.setUserId(userId);
			coupon.setBookId(bookId);
			coupon.setTotal(total);
			couponDao.insert(coupon);
			//钱包表递减
			moneyDao.update(userId, total);
		}
		return true;
	}
}

此时,只有insert方法所有代码执行成功才会提交事务,否则会回滚。
注意:
(1)一个类含有@Transactional注解修饰的方法,则Spring框架自动为该类创建代理对象,默认使用JDK创建代理对象,可以通过添加<aop:aspectj-autoproxy proxy-target-class=“true”/>使用CGLib创建代理对象,此时需要添加aspectjweaver-x.x.x.jar包。
(2)不能在protected、默认或者private的方法上使用@Transactional注解,否则无效。

三、@Transactional注解属性:
属性 说明
timeout 设置一个事务所允许执行的最长时长(单位:秒),如果超过该时长且事务还没有完成,则自动回滚事务且出现org.springframework.transaction.TransactionTimedOutException异常
readOnly 事务只读,指对事务性资源进行只读操作,不能修改。
rollbackFor 指定对哪些异常回滚事务。默认情况下,如果没有抛出任何异常,或者抛出了检查时异常,依然提交事务。而rollbackFor可以控制事务在抛出某些检查时异常时回滚事务。
propagation 指定事务传播行为,一个事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
isolation 指定事务隔离级别。

1、timeout:超过该时长且事务还没有完成,则自动回滚事务。

@Service
public class CouponService implements ICouponService {

	@Autowired
	private IBookDao bookDao;
	@Autowired
	private IMoneyDao moneyDao;
	@Autowired
	private ICouponDao couponDao;
	
	@Transactional(timeout = 3 /*如果超过3s时间则回滚*/)
	public boolean insert(String userId,String bookId, int count){
		
		if(bookDao.enough(bookId, count)) {//书籍足够
			//书籍表库存递减
			bookDao.update(bookId, count);
		}
		double price = bookDao.getPrice(bookId);
		double total = price*count;
		
		try {
			Thread.sleep(4000);//等待4s
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		if(moneyDao.enough(userId, total)) {//余额足够
			//订单表添加数据
			Coupon coupon = new Coupon();
			coupon.setId(UUID.randomUUID().toString());
			coupon.setUserId(userId);
			coupon.setBookId(bookId);
			coupon.setTotal(total);
			couponDao.insert(coupon);
			//钱包表递减
			moneyDao.update(userId, total);
		}
		return true;
	}
}

抛出异常:
Spring框架事务 @Transactional常用属性说明
2、readOnly:事务只读,指对事务性资源进行只读操作,不能修改。

@Transactional(readOnly = true  /*只读,不能修改*/)

抛出异常:
Spring框架事务 @Transactional常用属性说明
3、rollbackFor:可以设置在抛出检查时异常时回滚事务。

@Transactional(rollbackFor= {MoneyException.class}  /*抛出检查时异常MoneyException也会回滚*/)

抛出检查时异常:
Spring框架事务 @Transactional常用属性说明
4、propagation:当一个事务中调用另一个事务时,默认将第二个事务与第一个事务算到一起,即如果第二个事务出错,第一个事务也会回滚。如果想让一个事务中调用另一个事务,这另一个事务将创建一个新的事务,则设置propagation为Propagation.REQUIRES_NEW

① 第一个事务,调用了insert方法:

//购物车购买
@Transactional
public boolean batch(String userId,Map<String,Integer> commodities) {
	Set<Entry<String, Integer>> set = commodities.entrySet();
	for (Entry<String, Integer> commodity : set) {
		String bookId = commodity.getKey();
		int count = commodity.getValue();
		couponService.insert(userId,bookId, count);
	}
	return true;
}

② 第二个事务,设置propagation为Propagation.REQUIRES_NEW,将创建一个新的事务:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public boolean insert(String userId,String bookId, int count){
	
	if(bookDao.enough(bookId, count)) {//书籍足够
		//书籍表库存递减
		bookDao.update(bookId, count);
	}
	double price = bookDao.getPrice(bookId);
	double total = price*count;
	if(moneyDao.enough(userId, total)) {//余额足够
		//订单表添加数据
		Coupon coupon = new Coupon();
		coupon.setId(UUID.randomUUID().toString());
		coupon.setUserId(userId);
		coupon.setBookId(bookId);
		coupon.setTotal(total);
		couponDao.insert(coupon);
		//钱包表递减
		moneyDao.update(userId, total);
	}
	return true;
}

此时,如果第二个事务抛出异常,则第一个事务不会回滚。

5、isolation:指定事务隔离级别,Spring定义了如下5种事务隔离级别:

事务隔离级别 介绍
DEFAULT 默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常为READ_COMMITTED。
READ_UNCOMMITTED 表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别可能出现脏读、不可重复读或幻读,因此很少使用该隔离级别。
READ_COMMITTED 表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,但可能出现不可重复读或幻读,这也是大多数情况下的推荐值。
REPEATABLE_READ 表示一个事务在整个过程中可以多次重复执行某个查询,且每次返回的记录都相同,除非数据被当前事务自生修改。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读,但可能出现幻读。
SERIALIZABLE 表示所有的事务依次逐个执行,事务之间互不干扰,该级别可以防止脏读、不可重复读和幻读,但是这将严重影响程序的性能,因此通常情况下也不会用到该级别。

注意:事务的隔离级别要得到底层数据库引擎的支持, 而不是应用程序或者框架的支持:Oracle 支持READ_COMMITED和SERIALIZABLE两种事务隔离级别,默认为READ_COMMITED;MySQL 支持READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ和SERIALIZABLE四种事务隔离级别,默认为:REPEATABLE_READ。

相关标签: SSM