提交 1c137c86 编写于 作者: S shuang.kou

Update spring-transaction.md

上级 84fa469d
## 什么是事务?
大家好,我是Guide哥,答应读者的 **Spring 事务**分析总结终于来了。这部分内容比较重要,不论是对于工作还是面试,但是网上比较好的参考资料比较少。
**事务是逻辑上的一组操作,要么都执行,要么都不执行。**
如果本文有任何不对或者需要完善的地方,请帮忙指出!Guide哥感激不尽!本文已经同步更新到我的 JavaGuide ,地址:
*Guide哥:大家应该都能背上面这句话了,下面我结合我们日常的真实开发来谈一谈!*
## 1. 什么是事务?
具体对应到我们日常开发过程中是这样的:**我们系统的每个业务方法可能包括了多个原子性的数据库操作,并且原子性的数据库操作是有依赖的,它们要不都执行,要不就都不执行。**
**事务是逻辑上的一组操作,要么都执行,要么都不执行。**
另外,需要格外注意的是:**事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的`innodb`引擎。但是,如果把数据库引擎变为myisam,那么程序也就不再支持事务了!**
_Guide 哥:大家应该都能背上面这句话了,下面我结合我们日常的真实开发来谈一谈。_
事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账1000元,这个转账会涉及到两个关键操作就是:将小明的余额减少1000元,将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。
**我们系统的每个业务方法可能包括了多个原子性的数据库操作,比如下面的 `savePerson()` 方法中就有两个原子性的数据库操作。这些原子性的数据库操作是有依赖的,它们要么都执行,要不就都不执行。**
```java
public void savePerson() {
personDao.save(person);
personDetailDao.save(personDetail);
}
```
另外,需要格外注意的是:**事务能否生效数据库引擎是否支持事务是关键。常用的 MySQL 数据库默认使用支持事务的`innodb`引擎。但是,如果把数据库引擎变为 myisam,那么程序也就不再支持事务了!**
事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账 1000 元,这个转账会涉及到两个关键操作就是:
1. 将小明的余额减少 1000 元
2. 将小红的余额增加 1000 元。
万一在这两个操作之间突然出现错误比如银行系统崩溃或者网络故障,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。
```java
public class OrdersService {
......@@ -31,33 +48,111 @@ public class OrdersService {
}
```
## 事物的特性(ACID)了解么?
另外,数据库事务的 ACID 四大特性是事务的基础,下面简单来了解一下。
## 2. 事物的特性(ACID)了解么?
![](https://imgkr.cn-bj.ufileos.com/bda7231b-ab05-4e23-95ee-89ac90ac7fcf.png)
- **原子性:** 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
- **一致性:** 执行事务前后,数据保持一致;
- **隔离性:** 并发访问数据库时,一个用户的事物不被其他事务所干扰也就是说多个事务并发执行时,一个事务的执行不应影响其他事务的执行;
- **持久性:** 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
- **持久性:** 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
## 详谈Spring对事务的支持
## 3. 详谈 Spring 对事务的支持
**再提醒一次:你的程序是否支持事务首先取决于数据库 ,比如使用 MySQL 的话,如果你选择的是 innodb 引擎,那么恭喜你,是可以支持事务的。但是,如果你的 MySQL 数据库使用的是 myisam 引擎的话,那不好意思,从根上就是不支持事务的。**
这里再多提一下一个非常重要的知识点: **MySQL 怎么保证原子性的?**
我们知道如果想要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行**回滚**,在 MySQL 中,恢复机制是通过 **回滚日志(undo log)** 实现的,所有事务进行的修改都会先先记录到这个回滚日志中,然后再执行相关的操作。如果执行过程中遇到异常的话,我们直接利用 **回滚日志** 中的信息将数据回滚到修改之前的样子即可!并且,回滚日志会先于数据持久化到磁盘上。这样就保证了即使遇到数据库突然宕机等情况,当用户再次启动数据库的时候,数据库还能够通过查询回滚日志来回滚将之前未完成的事务。
### 3.1. Spring 支持两种方式的事务管理
#### 1).编程式事务管理
通过 `TransactionTemplate`或者`TransactionManager`手动管理事务,实际应用中很少使用,但是对于你理解 Spring 事务管理原理有帮助。
使用`TransactionTemplate` 进行编程式事务管理的示例代码如下:
```java
@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
try {
// .... 业务代码
} catch (Exception e){
//回滚
transactionStatus.setRollbackOnly();
}
}
});
}
```
使用 `TransactionManager` 进行编程式事务管理的示例代码如下:
```java
@Autowired
private PlatformTransactionManager transactionManager;
public void testTransaction() {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// .... 业务代码
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
}
}
```
#### 2)声明式事务管理
推荐使用(代码侵入性最小),实际是通过 AOP 实现(基于`@Transactional` 的全注解方式使用最多)。
使用 `@Transactional`注解进行事务管理的示例代码如下:
```java
@Transactional(propagation=propagation.PROPAGATION_REQUIRED)
public void aMethod {
//do something
B b = new B();
C c = new C();
b.bMethod();
c.cMethod();
}
```
### Spring支持两种方式的事务管理
### 3.2. Spring 事务管理接口介绍
- **编程式事务管理:** 通过Transaction Template手动管理事务,实际应用中很少使用,
- **置声明式事务:** 推荐使用(代码侵入性最小),实际是通过AOP实现(基于` @Transactional` 的全注解方式使用最多,后文主要介绍这个注解的使用)。
Spring 框架中,事务管理相关最重要的 3 个接口如下:
### Spring事务管理接口介绍
- **`PlatformTransactionManager`**: (平台)事务管理器,Spring 事务策略的核心。
- **`TransactionDefinition`**: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。
- **`TransactionStatus`**: 事务运行状态。
Spring 框架中,事务管理相关最重要的3个接口如下:
我们可以把 **`PlatformTransactionManager`** 接口可以被看作是事务上层的管理者,而**`TransactionDefinition`****`TransactionStatus`**这两个接口可以看作是事物的描述。
- **`PlatformTransactionManager`**: (平台)事务管理器,Spring事务策略的核心。
- **`TransactionDefinition`**: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)
- **`TransactionStatus`**: 事务运行状态
**`PlatformTransactionManager`** 会根据**`TransactionDefinition`** 的定义比如事务超时时间、隔离界别、传播行为等来进行事务管理 ,而 **`TransactionStatus`**接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等。
#### PlatformTransactionManager
#### 3.2.1. PlatformTransactionManager:事务管理接口
**Spring并不直接管理事务,而是提供了多种事务管理器** 。Spring事务管理器的接口是: **org.springframework.transaction.PlatformTransactionManager** ,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
**Spring 并不直接管理事务,而是提供了多种事务管理器** 。Spring 事务管理器的接口是: **`PlatformTransactionManager`**
通过这个接口,Spring 为各个平台如 JDBC(`DataSourceTransactionManager`)、Hibernate(`HibernateTransactionManager`)、JPA(`JpaTransactionManager`)等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
**`PlatformTransactionManager` 接口的具体实现如下:**
![](https://imgkr.cn-bj.ufileos.com/ae964c2c-7289-441c-bddd-511161f51ee1.png)
`PlatformTransactionManager`接口中定义了三个方法:
......@@ -77,17 +172,25 @@ public interface PlatformTransactionManager {
```
#### TransactionDefinition
**这里多插一嘴。为什么要定义或者说抽象出来`PlatformTransactionManager`这个接口呢?**
主要是因为要将事务管理行为抽象出来,然后不同的平台去实现它,这样我们可以保证提供给外部的行为不变,方便我们扩展。我前段时间分享过:**“为什么我们要用接口?”**
<img src="https://imgkr.cn-bj.ufileos.com/ed279f05-f5ad-443e-84e9-513a9e777139.png" style="zoom:50%;" />
#### 3.2.2. TransactionDefinition:事务属性
事务管理器接口 **`PlatformTransactionManager`** 通过 **`getTransaction(TransactionDefinition definition)`** 方法来得到一个事务,这个方法里面的参数是 **`TransactionDefinition`**类 ,这个类就定义了一些基本的事务属性。
**那么什么是事务属性呢?**
那么什么是 **事务属性** 呢?
事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。
事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。事务属性包含了5个方面。
事务属性包含了 5 个方面:
![](https://imgkr.cn-bj.ufileos.com/a616b84d-9eea-4ad1-b4fc-461ff05e951d.png)
`TransactionDefinition` 接口中定义了5个方法以及一些表示事务属性的常量比如隔离级别、传播行为等等。
`TransactionDefinition` 接口中定义了 5 个方法以及一些表示事务属性的常量比如隔离级别、传播行为等等。
```java
package org.springframework.transaction;
......@@ -122,13 +225,13 @@ public interface TransactionDefinition {
}
```
#### TransactionStatus
#### 3.2.3. TransactionStatus:事务状态
`TransactionStatus`接口用来记录事务的状态 该接口定义了一组方法,用来获取或判断事务的相应状态信息.
`TransactionStatus`接口用来记录事务的状态 该接口定义了一组方法,用来获取或判断事务的相应状态信息
`PlatformTransactionManager.getTransaction(…) `方法返回一个 `TransactionStatus` 对象。返回的 `TransactionStatus` 对象可能代表一个新的或已经存在的事务(如果在当前调用堆栈有一个符合条件的事务)
`PlatformTransactionManager.getTransaction(…)`方法返回一个 `TransactionStatus` 对象
**TransactionStatus接口接口内容如下:**
**TransactionStatus 接口接口内容如下:**
```java
public interface TransactionStatus{
......@@ -137,20 +240,22 @@ public interface TransactionStatus{
void setRollbackOnly(); // 设置为只回滚
boolean isRollbackOnly(); // 是否为只回滚
boolean isCompleted; // 是否已完成
}
}
```
### 事务属性详解
### 3.3. 事务属性详解
_实际业务开发中,大家一般都是使用 `@Transactional` 注解来开启事务,很多人并不清楚这个参数里面的参数是什么意思,有什么用。为了更好的在项目中使用事务管理,强烈推荐好好阅读一下下面的内容。_
#### 事务传播行为
#### 3.3.1. 事务传播行为
**事务传播行为是为了解决业务层方法之间互相调用的事务问题**
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
举个例子!
**举个例子!**
我们在 A 类的`aMethod()`方法中调用了 B 类的 `bMethod()` 方法。这个时候就涉及到业务层方法之间互相调用的事务问题。我们的 `bMethod() `如果发生异常需要回滚,如何配置事务传播行为才能让 `aMethod()`也跟着回滚呢?
我们在 A 类的`aMethod()`方法中调用了 B 类的 `bMethod()` 方法。这个时候就涉及到业务层方法之间互相调用的事务问题。如果我们的 `bMethod()`如果发生异常需要回滚,如何配置事务传播行为才能让 `aMethod()`也跟着回滚呢?这个时候就需要事务传播行为的知识了,如果你不知道的话一定要好好看一下。
```java
Class A {
......@@ -161,10 +266,10 @@ Class A {
b.bMethod();
}
}
Class B {
@Transactional(propagation=propagation.xxx)
public void bMethod {
public void bMethod {
//do something
}
}
......@@ -185,7 +290,7 @@ public interface TransactionDefinition {
}
```
不过如此,为了方便使用,Spring会相应地定义了一个枚举类:`Propagation`
不过如此,为了方便使用,Spring 会相应地定义了一个枚举类:`Propagation`
```java
package org.springframework.transaction.annotation;
......@@ -197,13 +302,13 @@ 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);
......@@ -243,10 +348,10 @@ Class A {
b.bMethod();
}
}
Class B {
@Transactional(propagation=propagation.PROPAGATION_REQUIRED)
public void bMethod {
public void bMethod {
//do something
}
}
......@@ -267,16 +372,16 @@ Class A {
b.bMethod();
}
}
Class B {
@Transactional(propagation=propagation.REQUIRES_NEW)
public void bMethod {
public void bMethod {
//do something
}
}
```
**4.`TransactionDefinition.PROPAGATION_NESTED`**:
**3.`TransactionDefinition.PROPAGATION_NESTED`**:
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于`TransactionDefinition.PROPAGATION_REQUIRED`。也就是说:
......@@ -297,36 +402,112 @@ Class A {
b.bMethod2();
}
}
Class B {
@Transactional(propagation=propagation.PROPAGATION_NESTED)
public void bMethod {
public void bMethod {
//do something
}
@Transactional(propagation=propagation.PROPAGATION_NESTED)
public void bMethod2 {
public void bMethod2 {
//do something
}
}
```
**5.`TransactionDefinition.PROPAGATION_MANDATORY`**
**4.`TransactionDefinition.PROPAGATION_MANDATORY`**
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
这个使用的很少,就不举例子来说了。
**若是错误的配置以下种事务传播行为,事务将不会发生回滚,这里不对照案例讲解了,使用的很少。**
**若是错误的配置以下3种事务传播行为,事务将不会发生回滚,这里不对照案例讲解了,使用的很少。**
- **`TransactionDefinition.PROPAGATION_SUPPORTS`**: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- **`TransactionDefinition.PROPAGATION_NOT_SUPPORTED`**: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- **`TransactionDefinition.PROPAGATION_NEVER`**: 以非事务方式运行,如果当前存在事务,则抛出异常。
#### 事务超时属性
更多关于事务传播行为的内容请看这篇文章:[《太难了~面试官让我结合案例讲讲自己对 Spring 事务传播行为的理解。》](http://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486668&idx=2&sn=0381e8c836442f46bdc5367170234abb&chksm=cea24307f9d5ca11c96943b3ccfa1fc70dc97dd87d9c540388581f8fe6d805ff548dff5f6b5b&token=1776990505&lang=zh_CN#rd)
#### 3.3.2 事务隔离级别
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
`TransactionDefinition` 接口中定义了五个表示隔离级别的常量:
```java
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`
```java
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` 隔离级别 Oracle 默认采用的 `READ_COMMITTED` 隔离级别.
- **`TransactionDefinition.ISOLATION_READ_UNCOMMITTED`** :最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,**可能会导致脏读、幻读或不可重复读**
- **`TransactionDefinition.ISOLATION_READ_COMMITTED`** : 允许读取并发事务已经提交的数据,**可以阻止脏读,但是幻读或不可重复读仍有可能发生**
- **`TransactionDefinition.ISOLATION_REPEATABLE_READ`** : 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,**可以阻止脏读和不可重复读,但幻读仍有可能发生。**
- **`TransactionDefinition.ISOLATION_SERIALIZABLE`** : 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,**该级别可以防止脏读、不可重复读以及幻读**。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
因为平时使用 MySQL 数据库比较多,这里再多提一嘴!
MySQL InnoDB 存储引擎的默认支持的隔离级别是 **`REPEATABLE-READ`(可重读)**。我们可以通过`SELECT @@tx_isolation;`命令来查看,如下:
```
mysql> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
```
这里需要注意的是:与 SQL 标准不同的地方在于 InnoDB 存储引擎在 **`REPEATABLE-READ`(可重读)**事务隔离级别下使用的是 Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以说 InnoDB 存储引擎的默认支持的隔离级别是 **`REPEATABLE-READ`(可重读)** 已经可以完全保证事务的隔离性要求,即达到了 SQL 标准的**`SERIALIZABLE`(可串行化)**隔离级别。
因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是**`READ-COMMITTED`(读取提交内容)** :,但是你要知道的是 InnoDB 存储引擎默认使用 **`REPEATABLE-READ`(可重读)**并不会什么任何性能上的损失。
更多关于事务隔离级别的内容请看:
1. [《一文带你轻松搞懂事务隔离级别(图文详解)》](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485085&idx=1&sn=01e5c29c49f32886bc897af7632b34ba&chksm=cea24956f9d5c040a07e4d335219f11f888a2d32444c16cade3f69c294ae0a1e416bcd221fb6&token=1613452699&lang=zh_CN&scene=21#wechat_redirect)
2. [面试官:你说对 MySQL 事务很熟?那我问你 10 个问题](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486625&idx=2&sn=e235dab2757739438b8f33d205a9327f&chksm=cea2436af9d5ca7c9a1a8db9d020f71205687beca23ac958f9c9a711ee0185cab30173ad2b1a&token=1776990505&lang=zh_CN#rd)
#### 3.3.3. 事务超时属性
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 `TransactionDefinition` 中以 int 的值来表示超时时间,其单位是秒,默认值为-1。
#### 3.3.3. 事务只读属性
```java
package org.springframework.transaction;
......@@ -337,43 +518,167 @@ public interface TransactionDefinition {
......
// 返回是否为只读事务,默认值为 false
boolean isReadOnly();
}
```
对于只有读取数据查询的事务,可以指定事务类型为readonly,即只读事务。只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操作的方法中。
对于只有读取数据查询的事务,可以指定事务类型为 readonly,即只读事务。只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操作的方法中。
很多人就会疑问了,为什么我一个数据查询操作还要启用事务支持呢?
拿 MySQL 的 innodb 举例子,根据官网 [https://dev.mysql.com/doc/refman/5.7/en/innodb-autocommit-commit-rollback.html](https://dev.mysql.com/doc/refman/5.7/en/innodb-autocommit-commit-rollback.html) 描述:
> MySQL默认对每一个新建立的连接都启用了`autocommit`模式。在该模式下,每一个发送到MySQL服务器的`sql`语句都会在一个单独的事务中进行处理,执行结束后会自动提交事务,并开启一个新的事务。
> MySQL 默认对每一个新建立的连接都启用了`autocommit`模式。在该模式下,每一个发送到 MySQL 服务器的`sql`语句都会在一个单独的事务中进行处理,执行结束后会自动提交事务,并开启一个新的事务。
但是,如果你给方法加上了`Transactional`注解的话,这个方法执行的所有`sql`会被放在一个事务中。如果声明了只读事务的话,数据库就会去优化它的执行,并不会带来其他的什么收益。
但是,如果你给方法加上了`Transactional`注解的话,这个方法执行的所有`sql`会被放在一个事务中。如果声明了只读事务的话,数据库就会去优化它的执行,并不会带来其他的什么收益。只读事务并不能避免幻读。如果不加`Transactional`,每条`sql`会开启一个单独的事务,中间被其它事务改了数据,都会实时读取到最新值。
如果不加`Transactional`,每条`sql`会开启一个单独的事务,中间被其它事务改了数据,都会实时读取到最新值。
分享一下关于事务只读属性,其他人的解答:
1. 如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;
2. 如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持
1. 如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持 SQL 执行期间的读一致性;
2. 如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询 SQL 必须保证整体的读一致性,否则,在前条 SQL 查询之后,后条 SQL 查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持
#### 3.3.4. 事务回滚规则
这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常(RuntimeException 的子类)时才会回滚,Error 也会导致事务回滚,但是,在遇到检查型(Checked)异常时不会回滚。
![](https://imgkr.cn-bj.ufileos.com/f6c6f0aa-0f26-49e1-84b3-7f838c7379d1.png)
如果你想要回滚你定义的特定的异常类型的话,可以这样:
```java
@Transactional(rollbackFor= MyException.class)
```
### 3.4. @Transactional 注解使用详解
#### 1) `@Transactional` 的作用范围
1. **方法** :推荐将注解使用于方法上,不过需要注意的是:**该注解只能应用到 public 方法上,否则不生效。**
2. **类** :如果这个注解使用在类上的话,表明该注解对该类中所有的 public 方法都生效。
3. **接口** :不推荐在接口上使用。
#### 事务回滚规则
#### 2) `@Transactional` 的常用配置参数
这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的)。 但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。
`@Transactional`注解源码如下,里面包含了基本事务属性的配置:
### @Transactional注解使用详解
```java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
```
**`@Transactional` 的常用配置参数总结(只列巨额 5 个我平时比较常用的):**
| 属性名 | 说明 |
| :---------- | :------------------------------------------------------------------------------------------- |
| propagation | 事务的传播行为,默认值为 REQUIRED,可选的值在上面介绍过 |
| isolation | 事务的隔离级别,默认值采用 DEFAULT,可选的值在上面介绍过 |
| timeout | 事务的超时时间,默认值为-1(不会超时)。如果超过该时间限制但事务还没有完成,则自动回滚事务。 |
| readOnly | 指定事务是否为只读事务,默认值为 false。 |
| rollbackFor | 用于指定能够触发事务回滚的异常类型,并且可以指定多个异常类型。 |
#### 3)`@Transactional` 事务注解原理
面试中在问 AOP 的时候可能会被问到的一个问题。简单说下吧!
@Transactional注解就代表支持事务管理,@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。如果这个注解在类上,那么表示该注解对于所有该类中的public方法都生效;如果注解出现在方法上,则代表该注解仅对该方法有效,会覆盖先前从类层次继承下来的注解。
我们知道,**`@Transactional` 的工作机制是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现了接口,会使用 CGLIB 动态代理。**
多提一嘴:`createAopProxy()` 方法 决定了是使用 JDK 还是 Cglib 来做动态代理,源码如下:
```java
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
.......
}
```
如果一个类或者一个类中的 public 方法上被标注`@Transactional` 注解的话,Spring 容器就会在启动的时候为其创建一个代理类,在调用被`@Transactional` 注解的 public 方法的时候,实际调用的是,`TransactionInterceptor` 类中的 `invoke()`方法。这个方法的作用就是在目标方法之前开启事务,方法执行过程中如果遇到异常的时候回滚事务,方法调用完成之后提交事务。
> `TransactionInterceptor` 类中的 `invoke()`方法内部实际调用的是 `TransactionAspectSupport` 类的 `invokeWithinTransaction()`方法。由于新版本的 Spring 对这部分重写很大,而且用到了很多响应式编程的知识,这里就不列源码了。
#### 4)Spring AOP 自调用问题
若同一类中的其他没有 `@Transactional` 注解的方法内部调用有 `@Transactional` 注解的方法,有`@Transactional` 注解的方法的事务会失效。
这是由于`Spring AOP`代理的原因造成的,因为只有当 `@Transactional` 注解的方法在类以外被调用的时候,Spring 事务管理才生效。
`MyService` 类中的`method1()`调用`method2()`就会导致`method2()`的事务失效。
```java
@Service
public class MyService {
private void method1() {
method2();
//......
}
@Transactional
public void method2() {
//......
}
}
```
## Reference
解决办法就是避免同一类中自调用或者使用 AspectJ 取代 Spring AOP 代理。
1. 可能是最漂亮的Spring事务管理详解:https://juejin.im/post/5b00c52ef265da0b95276091
2. Spring 事务管理机制概述 : [https://blog.csdn.net/justloveyou_/article/details/73733278https://blog.csdn.net/justloveyou_/article/details/73733278](https://blog.csdn.net/justloveyou_/article/details/73733278https://blog.csdn.net/justloveyou_/article/details/73733278)
3. [总结]Spring事务管理中@Transactional的参数:[http://www.mobabel.net/spring事务管理中transactional的参数/](http://www.mobabel.net/spring事务管理中transactional的参数/)
4. 透彻的掌握 Spring 中@transactional 的使用: [https://www.ibm.com/developerworks/cn/java/j-master-spring-transactional-use/index.html](https://www.ibm.com/developerworks/cn/java/j-master-spring-transactional-use/index.html)
5. Spring事务的传播特性:[https://github.com/love-somnus/Spring/wiki/Spring事务的传播特性](https://github.com/love-somnus/Spring/wiki/Spring事务的传播特性)
6. [Spring事务传播行为详解](https://segmentfault.com/a/1190000013341344)[https://segmentfault.com/a/1190000013341344](https://segmentfault.com/a/1190000013341344)
7.
#### 5) `@Transactional` 的使用注意事项总结
1. `@Transactional` 注解只有作用到 public 方法上事务才生效,不推荐在接口上使用;
2. 避免同一个类中调用 `@Transactional` 注解的方法,这样会导致事务失效;
3. 正确的设置 `@Transactional` 的 rollbackFor 和 propagation 属性,否则事务可能会回滚失败
4. ......
## 4. Reference
3. [总结]Spring 事务管理中@Transactional 的参数:[http://www.mobabel.net/spring 事务管理中 transactional 的参数/](http://www.mobabel.net/spring事务管理中transactional的参数/)
4. Spring 官方文档:[https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/transaction.html](https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/transaction.html)
5. 《Spring5 高级编程》
6. 透彻的掌握 Spring 中@transactional 的使用: [https://www.ibm.com/developerworks/cn/java/j-master-spring-transactional-use/index.html](https://www.ibm.com/developerworks/cn/java/j-master-spring-transactional-use/index.html)
7. Spring 事务的传播特性:[https://github.com/love-somnus/Spring/wiki/Spring 事务的传播特性](https://github.com/love-somnus/Spring/wiki/Spring事务的传播特性)
8. [Spring 事务传播行为详解](https://segmentfault.com/a/1190000013341344)[https://segmentfault.com/a/1190000013341344](https://segmentfault.com/a/1190000013341344)
9. 全面分析 Spring 的编程式事务管理及声明式事务管理:[https://www.ibm.com/developerworks/cn/education/opensource/os-cn-spring-trans/index.html](https://www.ibm.com/developerworks/cn/education/opensource/os-cn-spring-trans/index.html)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册