Spring框架事务 @Transactional常用属性说明
一、什么是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配置文件:
<?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;
}
}
抛出异常:
2、readOnly:事务只读,指对事务性资源进行只读操作,不能修改。
@Transactional(readOnly = true /*只读,不能修改*/)
抛出异常:
3、rollbackFor:可以设置在抛出检查时异常时回滚事务。
@Transactional(rollbackFor= {MoneyException.class} /*抛出检查时异常MoneyException也会回滚*/)
抛出检查时异常:
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。
上一篇: 第一次参加数学建模竞赛如何夺取一等奖