Spring-- 事务
文章目录
前言
事务是逻辑上的一组操作,要么都执行,要么都不执行。
**注意:**事务能否生效数据库引擎是否支持事务是关键。比如常用的 MySQL 数据库默认使用支持事务的innodb
引擎。但是,如果把数据库引擎变为 myisam
,那么程序也就不再支持事务了!
事务的特性(ACID)
原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
一致性: 执行事务前后,数据保持一致;
隔离性: 并发访问数据库时,一个用户的事物不被其他事务所干扰也就是说多个事务并发执行时,一个事务的执行不应影响其他事务的执行;
持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
Spring对事务的支持
事务管理方式
编程式事务管理
通过 TransactionTemplate
或者TransactionManager
手动管理事务,实际应用中很少使用,但是对于你理解 Spring 事务管理原理有帮助。
使用TransactionTemplate
进行编程式事务管理的示例代码如下:
@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 实现(基于@Transactional
的全注解方式使用最多)。
使用 @Transactional
注解进行事务管理的示例代码如下:
@Transactional(propagation=propagation.PROPAGATION_REQUIRED)
public void aMethod {
//do something
B b = new B();
C c = new C();
b.bMethod();
c.cMethod();
}
Spring 事务管理接口
Spring 框架中,事务管理相关最重要的 3 个接口如下:
PlatformTransactionManager
: (平台)事务管理器,Spring 事务策略的核心。TransactionDefinition
: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。TransactionStatus
: 事务运行状态。
我们可以把 PlatformTransactionManager
接口可以被看作是事务上层的管理者,而 TransactionDefinition
和 TransactionStatus
这两个接口可以看作是事物的描述。
PlatformTransactionManager
会根据 TransactionDefinition
的定义比如事务超时时间、隔离界别、传播行为等来进行事务管理 ,而 TransactionStatus
接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等。
PlatformTransactionManager:事务管理接口
Spring 并不直接管理事务,而是提供了多种事务管理器 。Spring 事务管理器的接口是: PlatformTransactionManager
。
通过这个接口,Spring 为各个平台如 JDBC(DataSourceTransactionManager
)、Hibernate(HibernateTransactionManager
)、JPA(JpaTransactionManager
)等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
PlatformTransactionManager
接口的具体实现如下:
PlatformTransactionManager
接口中定义了三个方法:
public interface PlatformTransactionManager {
//获得事务
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
//提交事务
void commit(TransactionStatus var1) throws TransactionException;
//回滚事务
void rollback(TransactionStatus var1) throws TransactionException;
}
TransactionDefinition:事务属性
事务管理器接口 PlatformTransactionManager
通过 getTransaction(TransactionDefinition definition)
方法来得到一个事务,这个方法里面的参数是 TransactionDefinition
类 ,这个类就定义了一些基本的事务属性。事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。
事务属性包含了 5 个方面:
TransactionDefinition
接口中定义了 5 个方法以及一些表示事务属性的常量比如隔离级别、传播行为等等。
后面会详细介绍
TransactionStatus:事务状态
TransactionStatus
接口用来记录事务的状态 该接口定义了一组方法,用来获取或判断事务的相应状态信息。
PlatformTransactionManager.getTransaction(…)
方法返回一个 TransactionStatus
对象。
TransactionStatus 接口接口内容如下:
public interface TransactionStatus{
boolean isNewTransaction(); // 是否是新的事物
boolean hasSavepoint(); // 是否有恢复点
void setRollbackOnly(); // 设置为只回滚
boolean isRollbackOnly(); // 是否为只回滚
boolean isCompleted; // 是否已完成
}
事务属性详解
事务传播行为
事务传播行为是为了解决业务层方法之间互相调用的事务问题。
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
在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 会相应地定义了一个枚举类:Propagation
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
(统一事务)
使用的最多的一个事务传播行为,也是默认的传播行为。也就是说:
-
如果外部方法没有开启事务的话,
Propagation.REQUIRED
修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。 -
如果外部方法开启事务并且被
Propagation.REQUIRED
的话,所有Propagation.REQUIRED
修饰的内部方法和外部方法均属于同一事务 ,只要一个方法回滚,整个事务均回滚。**举个例子:**如果我们上面的
aMethod()
和bMethod()
使用的都是PROPAGATION_REQUIRED
传播行为的话,两者使用的就是同一个事务,只要其中一个方法回滚,整个事务均回滚。
Class A {
@Transactional(propagation=propagation.PROPAGATION_REQUIRED)
public void aMethod {
//do something
B b = new B();
b.bMethod();
}
}
Class B {
@Transactional(propagation=propagation.PROPAGATION_REQUIRED)
public void bMethod {
//do something
}
}
TransactionDefinition.PROPAGATION_REQUIRES_NEW
(新事务)
创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW
修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
举个例子:如果我们上面的bMethod()
使用PROPAGATION_REQUIRES_NEW
事务传播行为修饰,aMethod
还是用PROPAGATION_REQUIRED
修饰的话。如果aMethod()
发生异常回滚,bMethod()
不会跟着回滚,因为 bMethod()
开启了独立的事务。但是,如果 bMethod()
抛出了未被捕获的异常并且这个异常满足事务回滚规则的话,aMethod()
同样也会回滚,因为这个异常被 aMethod()
的事务管理机制检测到了。
Class A {
@Transactional(propagation=propagation.PROPAGATION_REQUIRED)
public void aMethod {
//do something
B b = new B();
b.bMethod();
}
}
Class B {
@Transactional(propagation=propagation.REQUIRES_NEW)
public void bMethod {
//do something
}
}
TransactionDefinition.PROPAGATION_NEST
(子事务)
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED
。也就是说:
- 在外部方法未开启事务的情况下
Propagation.NESTED
和Propagation.REQUIRED
作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。 - 如果外部方法开启事务的话,
Propagation.NESTED
修饰的内部方法属于外部事务的子事务,外部主事务回滚的话,子事务也会回滚,而内部子事务可以单独回滚而不影响外部主事务和其他子事务。
这里还是简单举个例子:
如果 aMethod()
回滚的话,bMethod()
和bMethod2()
都要回滚,而bMethod2()
回滚的话,并不会造成 aMethod()
和bMethod()
回滚。
Class A {
@Transactional(propagation=propagation.PROPAGATION_REQUIRED)
public void aMethod {
//do something
B b = new B();
b.bMethod();
b.bMethod2();
}
}
Class B {
@Transactional(propagation=propagation.PROPAGATION_NESTED)
public void bMethod {
//do something
}
@Transactional(propagation=propagation.PROPAGATION_NESTED)
public void bMethod2 {
//do something
}
}
TransactionDefinition.PROPAGATION_MANDATORY
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(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;
......
}
和事务传播行为这块一样,为了方便使用,Spring 也相应地定义了一个枚举类:Isolation
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;
}
}
-
ISOLATION_DEFAULT
使用后端数据库默认的隔离级别,MySQL 默认采用的REPEATABLE_READ
隔离级别 Oracle 默认采用的READ_COMMITTED
隔离级别 -
ISOLATION_READ_UNCOMMITTED
最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读 -
ISOLATION_READ_COMMITTED
允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 -
ISOLATION_REPEATABLE_READ
对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生 -
ISOLATION_SERIALIZABLE
最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别
事务超时属性
事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition
中以 int 的值来表示超时时间,其单位是秒,默认值为-1
事务只读属性
public interface TransactionDefinition {
......
// 返回是否为只读事务,默认值为 false
boolean isReadOnly();
}
对于只有读取数据查询的事务,可以指定事务类型为 readonly,即只读事务。只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操作的方法中。
“
MySQL 默认对每一个新建立的连接都启用了
autocommit
模式。在该模式下,每一个发送到 MySQL 服务器的sql
语句都会在一个单独的事务中进行处理,执行结束后会自动提交事务,并开启一个新的事务。
但是,如果你给方法加上了Transactional
注解的话,这个方法执行的所有sql
会被放在一个事务中。如果声明了只读事务的话,数据库就会去优化它的执行,并不会带来其他的什么收益。
如果不加Transactional
,每条sql
会开启一个单独的事务,中间被其它事务改了数据,都会实时读取到最新值。
分享一下关于事务只读属性,其他人的解答:
- 如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持 SQL 执行期间的读一致性;
- 如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询 SQL 必须保证整体的读一致性,否则,在前条 SQL 查询之后,后条 SQL 查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持
事务回滚规则
这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常(RuntimeException 的子类)时才会回滚,Error 也会导致事务回滚,但是,在遇到检查型(Checked)异常时不会回滚。
如果你想要回滚你定义的特定的异常类型的话,可以这样:
@Transactional(rollbackFor= MyException.class)
@Transactional 注解使用详解
@Transactional
的使用注意事项总结
@Transactional
注解只有作用到 public 方法上事务才生效,不推荐在接口上使用;- 避免同一个类中调用
@Transactional
注解的方法,这样会导致事务失效; - 正确的设置
@Transactional
的 rollbackFor 和 propagation 属性,否则事务可能会回滚失败
参考
https://juejin.im/post/5ebe682c5188256d8a22940f