什么是事务?
事务是逻辑上的一组操作,要么都执行,要么都不执行。事务能否生效数据库引擎是否支持事务是关键。比如常用的MySQL数据库默认使用支持事务的innodb引擎。但是如果把数据库引擎变为myisam,那么程序就不再支持事务了。
事务的特性ACID
- 原子性(Atomicity):一个事务中所有操作要么全部完成,要么全部不完成。不会结束在中间某个状态。如果执行过程中发生错误,那么会回滚到事务开始之前的状态,就像是这个事务从来没有执行过一样。
- 一致性(Consistency):在事务开始之前和结束之后,数据库的完整性没有被破坏。
- 隔离性(Isolation):数据库允许多个并发事务同时对数据进行读写和修改。隔离性可以防止多个事务并发执行时由于交叉执行导致数据的不一致。事务的隔离级别分为不同级别:包括未提交读,提交读,可重复读和串行化。
- 持久性(Durability):事务处理结束后,对数据的修改是永久的。即使系统故障也不会丢失。
Spring对事务的支持
MySQL中保证事务原子性是对已经执行的操作进行回滚。
通过回滚日志实现的。
所有事务进行的修改都会先记录到这个回滚日志中。然后再执行相应的操作。如果执行过程中发生异常,我们利用回滚日志中的信息将数据回滚到修改之前的样子。
回滚日志会先于数据持久化到磁盘上,这样就保证了数据库突然宕机,当用户再次启动数据库的时候,还能通过查询回滚日志来回滚之前未完成的事务。
Spring支持两种方式的事务管理
编程式事务管理
其实我们可以理解成手动提交事务和回滚事务。虽然很少使用但是我们可以了解一下。有两种调用方式:TransactionTemplate或者TransactionManager手动管理事务。使用demo如下:
@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
try {
// .... 业务代码
} catch (Exception e){
//回滚
transactionStatus.setRollbackOnly();
}
}
});
}
TransactionManager使用方式如下:
@Autowired
private PlatformTransactionManager transactionManager;
public void testTransaction() {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// .... 业务代码
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
}
}
其实我个人感觉这么些虽然很麻烦,但是灵活多了。而另一种常用的方式是声明式事务管理。虽然使用更加简单,但是其实除了问题挺不容易排查的。
声明式事务管理
这种方式代码入侵性最小。实际上是通过AOP实现的。下面是使用demo:
@Transactional(propagation = Propagation.REQUIRED)
public void aMethod {
//do something
B b = new B();
C c = new C();
b.bMethod();
c.cMethod();
}
Spring事务管理接口介绍
Spring框架中,事务管理相关最重要的接口有三个:
- PlatformTransactionManager:事务管理器,Spring事务策略的核心。
- TransactionDefinition:事务定义信息(事务隔离级别,传播行为,超时。只读,回滚规则)
-
TransactionStatus:事务运行状态。
我们可以把PlatformTransactionManager 接口看作是事务上层的管理者。而TransactionDefinition和TransactionStatus这两个接口看作是事务的描述。
PlatformTransactionManager 会根据TransactionDefinition的定义来进行事务管理。TransactionStatus接口提供了一些方法来获取事务相应的状态。比如是否是新事务,是否可以回滚等。
PlatformTransactionManager:事务管理接口
Spring并不直接管理事务,而是提供了多种事务管理器。这个管理器的接口就是PlatformTransactionManager。
通过这个接口,Spring为各个平台如JDBC,Hibernate,JPA等提供了对应的事务管理器。但是具体的实现就是各个平台自己的事情了。
TransactionDefinition:事务属性
事务管理器接口通过getTransaction方法来得到一个事务。这个方法里的参数是
TransactionDefinition类。这个类就是定义了一些基本的事务属性。
事务属性包含五个方面:
- 隔离级别
- 传播行为
- 回滚规则
- 是否只读
- 事务超时
TransactionStatus:事务状态
TransactionStatus接口用来记录事务的状态,该接口定义了一组方法,用来获取或者判断事务相应的状态信息。内容如下:
public interface TransactionStatus{
boolean isNewTransaction(); // 是否是新的事务
boolean hasSavepoint(); // 是否有恢复点
void setRollbackOnly(); // 设置为只回滚
boolean isRollbackOnly(); // 是否为只回滚
boolean isCompleted; // 是否已完成
}
事务属性详解
实际开发中大家一般都使用@Transactional注解来开启事务。这个注解里有一些参数,下面是介绍。
事务传播行为
事务传播行为是为了解决业务层方法之间互相调用的事务问题。
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如可以继续在现有事务中运行,也可以开启一个新事务,在自己的事务中运行。
举个例子:我们在A类的a方法中调用了B类的b方法,这个时候如果b方法异常,我们要a也回滚么?如何让a回滚?这就是事务传播行为的知识了。
在TransactionDefinition中定义了以下几个常量:
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
......
}
当然了spring也定义了枚举类:
package org.springframework.transaction.annotation;
import org.springframework.transaction.TransactionDefinition;
public enum Propagation {
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
NEVER(TransactionDefinition.PROPAGATION_NEVER),
NESTED(TransactionDefinition.PROPAGATION_NESTED);
private final int value;
Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
下面一个一个说:
-
TransactionDefinition.PROPAGATION_REQUIRED
这个是使用最多的事务传播行为。@Transactional注解默认使用的就是这个事务传播行为。如果当前存在事务,则加入该事务,如果当前没有事务,则创建一个新事务。也就是说:- 如果外部方法没有开启事务的话,PROPAGATION_REQUIRED修饰的内部方法会新开启自己的事务,且开启是事务相互独立,互不干扰。
- 如果外部方法开启事务并且被PROPAGATION_REQUIRED修饰的话,所有PROPAGATION_REQUIRED修饰的内部方法和外部方法属于同一事务,只要一个方法回滚,整个事务回滚。
TransactionDefinition.PROPAGATION_REQUIRES_NEW
创建一个新事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,PROPAGATION_REQUIRES_NEW修饰的内部方法都会开启自己的事务。且开启的事务相互独立,互不干扰。举个例子,如图的两个方法。如果a发生异常回滚,b不会回滚。但是如果b发生异常回滚并且把异常抛出来了,a检测到异常也会回滚。
@Service
Class A {
@Autowired
B b;
@Transactional(propagation = Propagation.REQUIRED)
public void aMethod {
//do something
b.bMethod();
}
}
@Service
Class B {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void bMethod {
//do something
}
}
-
TransactionDefinition.PROPAGATION_NESTED
如果当前存在事务,就在嵌套事务内执行。如果当前没有事务,就开启一个事务。也就是说:- 在外部方法开启事务的情况下,在内部开启一个新的事务。作为嵌套事务存在。
- 如果外部方法没有事务,则单独开启一个事务。
简单来说嵌套事务就是内层事务回滚外层也会回滚。上面的PROPAGATION_REQUIRES_NEW是要把异常抛给a,让a捕获才能回滚,否则不回滚的。而嵌套事务只要内层回滚外层都会回滚,这就是区别。
TransactionDefinition.PROPAGATION_MANDATORY
这个是当前存在事务则加入。不存在事务则抛异常。这个使用的很少。TransactionDefinition.PROPAGATION_SUPPORTS
当前存在事务则加入该事务,当前不存在事务就以非事务的方式运行TransactionDefinition.PROPAGATION_NOT_SUPPORTED
以非事务方式运行,如果当前存在事务则挂起TransactionDefinition.PROPAGATION_NEVER
以非事务方式运行,如果当前存在事务则报错
事务隔离级别
TransactionDefinition接口定义了五个隔离级别的常量,当然也有对应的枚举。
public interface TransactionDefinition {
......
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
......
}
public enum Isolation {
DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),
READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),
READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),
REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),
SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);
private final int value;
Isolation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
- TransactionDefinition.ISOLATION_DEFAULT
使用后端数据库默认的隔离级别。MySQL默认采用的是REPEATABLE_READ 隔离级别 - TransactionDefinition.ISOLATION_READ_UNCOMMITTED
最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更。可能会导致脏读,幻读或者不可重复读。 - TransactionDefinition.ISOLATION_READ_COMMITTED
允许读取并发事务已经提交的数据。可以阻止脏读,但是幻读和不可重复读仍然有可能发生 - TransactionDefinition.ISOLATION_REPEATABLE_READ
对同一字段的多次读取结果是一致的。除非数据是被本身事务所修改。可以阻止脏读和不可重复读。但是可能出现幻读。 - TransactionDefinition.ISOLATION_SERIALIZABLE
最高隔离级别,完全服从ACID。所有的事务逐个执行。事务之间不存在干扰,但是严重影响性能,一般也不会使用该级别。
事务超时属性
指一个事务允许执行的最长时间。如果超过该时间限制事务还没完成,则自动回滚。在TransactionDefinition 中int值表示时间,单位是秒。默认是-1.表示该事务没有超时时间。
事务只读属性
对于只有查询功能的事务,可以指定类型为readonly。即只读事务。只读事务不涉及到数据的修改,数据库会有一些优化手段。适合用在有多条数据库查询操作的方法中。只读为什么还要事务呢?
打个比方,现在一个查询功能是同时查询汇总和每条详细数据的。如果我们先查询了汇总,总金额是100.然后查询详细数据,在汇总之后,查询详细之前多了一笔金额20.可能查出来的详细数据就变成120.这样汇总和详细数据是不一致的。
而当我们启动了只读事务。起码可以保证我们读的汇总和详情是一致的。
事务回滚规则
默认情况下事务只有遇到运行期一场或者Error才会导致事务回滚。但是在遇到检查型异常时不会回滚。如果想要回滚特定的异常类型的话,可以指定rollbackFor属性。
@Transactional注解使用详解
@Transactional的作用范围
- 方法: 推荐将注解使用在方法上,不过需要注意的是:该注解只能使用在public方法上,否则不生效
- 类:如果这个注解使用在类上的话,该类的素有public方法都生效
- 接口:不推荐在接口上使用
@Transactional的常用配置参数
- propagation:事务的传播行为,默认值是REQUIRED.可选的值上面说过了
- isolation:事务的隔离级别,默认是default,可选的值上面说过了
- timeout:事务的超时时间,默认值-1.就是没有超时限制。
- readOnly:事务是否只读,默认是false
- rollbackFor:指定能够触发事务回滚的异常类型。可以指定多个异常类型。
@Transactional事务注解原理
上面简单说过这个注解的实现是基于AOP。而AOP又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用JDK的动态代理,如果目标对象没有实现接口,会使用CGLIB动态代理。
如果一个类或者一个类中的public方法被标注@Transactional注解的话,spring容器就会在启动的时候为其创建一个代理类。在调用被@Transactional注解的public方法时,实际上调用的是TransactionInterceptor类中的Invoke方法。这个方法是作用就是在目标方法之前开启事务,方法执行过程中如果遇到异常的时候回滚事务,方法调用完成之后提交事务。
Spring AOP自调用问题
若同一类中的其它没有@Transactional注解的方法内部调用有@Transactional注解的方法,有@Transactional注解的方法的事务会失效。这是由于Spring AOP代理的原因造成的。因为只有当@Transactional注解的方法在类以外被调用的时候,Spring事务管理才生效。
@Transactional的使用注意事项总结
- @Transactional注解只作用到public方法上事务才会生效,不推荐使用在接口上。
- 避免同一个类中调用@Transactional注解的方法,这样会导致事务失效。
- 正确的设置@Transactional的rollbackFor和propagation属性,否则事务可能回滚失败。
- 被@Transactional注解的方法所在的类必须被spring管理,否则不生效。
- 底层使用的数据库必须支持事务机制,否则不生效。
本篇笔记就整理到这里,如果稍微帮到你了记得点个喜欢点个关注,也祝大家工作顺顺利利~!