mirror of
https://github.com/Snailclimb/JavaGuide
synced 2025-06-16 18:10:13 +08:00
380 lines
17 KiB
Markdown
380 lines
17 KiB
Markdown
## 什么是事务?
|
||
|
||
**事务是逻辑上的一组操作,要么都执行,要么都不执行。**
|
||
|
||
*Guide哥:大家应该都能背上面这句话了,下面我结合我们日常的真实开发来谈一谈!*
|
||
|
||
具体对应到我们日常开发过程中是这样的:**我们系统的每个业务方法可能包括了多个原子性的数据库操作,并且原子性的数据库操作是有依赖的,它们要不都执行,要不就都不执行。**
|
||
|
||
另外,需要格外注意的是:**事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的`innodb`引擎。但是,如果把数据库引擎变为myisam,那么程序也就不再支持事务了!**
|
||
|
||
事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账1000元,这个转账会涉及到两个关键操作就是:将小明的余额减少1000元,将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。
|
||
|
||
```java
|
||
public class OrdersService {
|
||
private AccountDao accountDao;
|
||
|
||
public void setOrdersDao(AccountDao accountDao) {
|
||
this.accountDao = accountDao;
|
||
}
|
||
|
||
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false, timeout = -1)
|
||
public void accountMoney() {
|
||
//小红账户多1000
|
||
accountDao.addMoney(1000,xiaohong);
|
||
//模拟突然出现的异常,比如银行中可能为突然停电等等
|
||
//如果没有配置事务管理的话会造成,小红账户多了1000而小明账户没有少钱
|
||
int i = 10 / 0;
|
||
//小王账户少1000
|
||
accountDao.reduceMoney(1000,xiaoming);
|
||
}
|
||
}
|
||
```
|
||
|
||
## 事物的特性(ACID)了解么?
|
||
|
||

|
||
|
||
- **原子性:** 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
|
||
- **一致性:** 执行事务前后,数据保持一致;
|
||
- **隔离性:** 并发访问数据库时,一个用户的事物不被其他事务所干扰也就是说多个事务并发执行时,一个事务的执行不应影响其他事务的执行;
|
||
- **持久性:** 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
|
||
|
||
## 详谈Spring对事务的支持
|
||
|
||
### Spring支持两种方式的事务管理
|
||
|
||
- **编程式事务管理:** 通过Transaction Template手动管理事务,实际应用中很少使用,
|
||
- **置声明式事务:** 推荐使用(代码侵入性最小),实际是通过AOP实现(基于` @Transactional` 的全注解方式使用最多,后文主要介绍这个注解的使用)。
|
||
|
||
### Spring事务管理接口介绍
|
||
|
||
Spring 框架中,事务管理相关最重要的3个接口如下:
|
||
|
||
- **`PlatformTransactionManager`**: (平台)事务管理器,Spring事务策略的核心。
|
||
- **`TransactionDefinition`**: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)
|
||
- **`TransactionStatus`**: 事务运行状态
|
||
|
||
#### PlatformTransactionManager
|
||
|
||
**Spring并不直接管理事务,而是提供了多种事务管理器** 。Spring事务管理器的接口是: **org.springframework.transaction.PlatformTransactionManager** ,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
|
||
|
||
`PlatformTransactionManager`接口中定义了三个方法:
|
||
|
||
```java
|
||
package org.springframework.transaction;
|
||
|
||
import org.springframework.lang.Nullable;
|
||
|
||
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个方法以及一些表示事务属性的常量比如隔离级别、传播行为等等。
|
||
|
||
```java
|
||
package org.springframework.transaction;
|
||
|
||
import org.springframework.lang.Nullable;
|
||
|
||
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;
|
||
int ISOLATION_DEFAULT = -1;
|
||
int ISOLATION_READ_UNCOMMITTED = 1;
|
||
int ISOLATION_READ_COMMITTED = 2;
|
||
int ISOLATION_REPEATABLE_READ = 4;
|
||
int ISOLATION_SERIALIZABLE = 8;
|
||
int TIMEOUT_DEFAULT = -1;
|
||
// 返回事务的传播行为,默认值为 REQUIRED。
|
||
int getPropagationBehavior();
|
||
//返回事务的隔离级别,默认值是 DEFAULT
|
||
int getIsolationLevel();
|
||
// 返回事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
|
||
int getTimeout();
|
||
// 返回是否为只读事务,默认值为 false
|
||
boolean isReadOnly();
|
||
|
||
@Nullable
|
||
String getName();
|
||
}
|
||
```
|
||
|
||
#### TransactionStatus
|
||
|
||
`TransactionStatus`接口用来记录事务的状态 该接口定义了一组方法,用来获取或判断事务的相应状态信息.
|
||
|
||
`PlatformTransactionManager.getTransaction(…) `方法返回一个 `TransactionStatus` 对象。返回的 `TransactionStatus` 对象可能代表一个新的或已经存在的事务(如果在当前调用堆栈有一个符合条件的事务)。
|
||
|
||
**TransactionStatus接口接口内容如下:**
|
||
|
||
```java
|
||
public interface TransactionStatus{
|
||
boolean isNewTransaction(); // 是否是新的事物
|
||
boolean hasSavepoint(); // 是否有恢复点
|
||
void setRollbackOnly(); // 设置为只回滚
|
||
boolean isRollbackOnly(); // 是否为只回滚
|
||
boolean isCompleted; // 是否已完成
|
||
}
|
||
```
|
||
|
||
### 事务属性详解
|
||
|
||
#### 事务传播行为
|
||
|
||
**事务传播行为是为了解决业务层方法之间互相调用的事务问题**。
|
||
|
||
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
|
||
|
||
举个例子!
|
||
|
||
我们在 A 类的`aMethod()`方法中调用了 B 类的 `bMethod()` 方法。这个时候就涉及到业务层方法之间互相调用的事务问题。我们的 `bMethod() `如果发生异常需要回滚,如何配置事务传播行为才能让 `aMethod()`也跟着回滚呢?
|
||
|
||
```java
|
||
Class A {
|
||
@Transactional(propagation=propagation.xxx)
|
||
public void aMethod {
|
||
//do something
|
||
B b = new B();
|
||
b.bMethod();
|
||
}
|
||
}
|
||
|
||
Class B {
|
||
@Transactional(propagation=propagation.xxx)
|
||
public void bMethod {
|
||
//do something
|
||
}
|
||
}
|
||
```
|
||
|
||
在`TransactionDefinition`定义中包括了如下几个表示传播行为的常量:
|
||
|
||
```java
|
||
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`
|
||
|
||
```java
|
||
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;
|
||
}
|
||
|
||
}
|
||
|
||
```
|
||
|
||
**正确的事务传播行为可能的值如下** :
|
||
|
||
**1.`TransactionDefinition.PROPAGATION_REQUIRED`**
|
||
|
||
使用的最多的一个事务传播行为,我们平时经常使用的`@Transactional`注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。也就是说:
|
||
|
||
1. 如果外部方法没有开启事务的话,`Propagation.REQUIRED`修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
|
||
2. 如果外部方法开启事务并且被`Propagation.REQUIRED`的话,所有`Propagation.REQUIRED`修饰的内部方法和外部方法均属于同一事务 ,只要一个方法回滚,整个事务均回滚。
|
||
|
||
举个例子:如果我们上面的`aMethod()`和`bMethod()`使用的都是`PROPAGATION_REQUIRED`传播行为的话,两者使用的就是同一个事务,只要其中一个方法回滚,整个事务均回滚。
|
||
|
||
```java
|
||
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
|
||
}
|
||
}
|
||
```
|
||
|
||
**`2.TransactionDefinition.PROPAGATION_REQUIRES_NEW`**
|
||
|
||
创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,`Propagation.REQUIRES_NEW`修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
|
||
|
||
举个例子:如果我们上面的`bMethod()`使用`PROPAGATION_REQUIRES_NEW`事务传播行为修饰,`aMethod`还是用`PROPAGATION_REQUIRED`修饰的话。如果`aMethod()`发生异常回滚,`bMethod()`不会跟着回滚,因为 `bMethod()`开启了独立的事务。但是,如果 `bMethod()`抛出了未被捕获的异常并且这个异常满足事务回滚规则的话,`aMethod()`同样也会回滚,因为这个异常被 `aMethod()`的事务管理机制检测到了。
|
||
|
||
```java
|
||
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
|
||
}
|
||
}
|
||
```
|
||
|
||
**4.`TransactionDefinition.PROPAGATION_NESTED`**:
|
||
|
||
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于`TransactionDefinition.PROPAGATION_REQUIRED`。也就是说:
|
||
|
||
1. 在外部方法未开启事务的情况下`Propagation.NESTED`和`Propagation.REQUIRED`作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。
|
||
2. 如果外部方法开启事务的话,`Propagation.NESTED`修饰的内部方法属于外部事务的子事务,外部主事务回滚的话,子事务也会回滚,而内部子事务可以单独回滚而不影响外部主事务和其他子事务。
|
||
|
||
这里还是简单举个例子:
|
||
|
||
如果 `aMethod()` 回滚的话,`bMethod()`和`bMethod2()`都要回滚,而`bMethod()`回滚的话,并不会造成 `aMethod()` 和`bMethod()`回滚。
|
||
|
||
```java
|
||
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
|
||
}
|
||
}
|
||
```
|
||
|
||
**5.`TransactionDefinition.PROPAGATION_MANDATORY`**
|
||
|
||
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
|
||
|
||
这个使用的很少,就不举例子来说了。
|
||
|
||
**若是错误的配置以下三种事务传播行为,事务将不会发生回滚,这里不对照案例讲解了,使用的很少。**
|
||
|
||
- **`TransactionDefinition.PROPAGATION_SUPPORTS`**: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
|
||
- **`TransactionDefinition.PROPAGATION_NOT_SUPPORTED`**: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
|
||
- **`TransactionDefinition.PROPAGATION_NEVER`**: 以非事务方式运行,如果当前存在事务,则抛出异常。
|
||
|
||
#### 事务超时属性
|
||
|
||
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
|
||
|
||
#### 事务只读属性
|
||
|
||
```java
|
||
package org.springframework.transaction;
|
||
|
||
import org.springframework.lang.Nullable;
|
||
|
||
public interface TransactionDefinition {
|
||
......
|
||
// 返回是否为只读事务,默认值为 false
|
||
boolean isReadOnly();
|
||
|
||
}
|
||
```
|
||
|
||
对于只有读取数据查询的事务,可以指定事务类型为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`语句都会在一个单独的事务中进行处理,执行结束后会自动提交事务,并开启一个新的事务。
|
||
|
||
但是,如果你给方法加上了`Transactional`注解的话,这个方法执行的所有`sql`会被放在一个事务中。如果声明了只读事务的话,数据库就会去优化它的执行,并不会带来其他的什么收益。只读事务并不能避免幻读。如果不加`Transactional`,每条`sql`会开启一个单独的事务,中间被其它事务改了数据,都会实时读取到最新值。
|
||
|
||
分享一下关于事务只读属性,其他人的解答:
|
||
|
||
1. 如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;
|
||
2. 如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持
|
||
|
||
|
||
#### 事务回滚规则
|
||
|
||
这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的)。 但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。
|
||
|
||
### @Transactional注解使用详解
|
||
|
||
@Transactional注解就代表支持事务管理,@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。如果这个注解在类上,那么表示该注解对于所有该类中的public方法都生效;如果注解出现在方法上,则代表该注解仅对该方法有效,会覆盖先前从类层次继承下来的注解。
|
||
|
||
## Reference
|
||
|
||
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.
|
||
|
||
|
||
|