- MySQL 事务
- Transaction Management (Spring事务之事务管理)
- 1. Declarative Transaction Management (声明式事务管理)
- 1.1 事务管理模型
- 1.2 Transaction Propagation (事务传播行为)
- 1.3 事务隔离级别
- 1.4 事务的超时时间
- 1.5 事务的只读属性
- 1.6 事务回滚策略
- 2. Programmatic Transaction Management (编程式事务管理)
- Spring事务常见问题
- 1. 说一说Spring的事务机制
- 2. 注解@Transactional常用设置
- 3. 事务回滚规则
- 4. Spring事务失效场景
- 5. 事务并发会产生什么问题?
MySQL事务:https://blog.csdn.net/weixin_45773632/article/details/122588818?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22122588818%22%2C%22source%22%3A%22weixin_45773632%22%7D&ctrtid=Q6u1b
Transaction Management (Spring事务之事务管理)在Spring中,事务有两种实现方式,分别是编程式事务管理和声明式事务管理两种方式。
-
编程式事务管理: 编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使TransactionTemplate。
-
声明式事务管理: 建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务管理不需要入侵代码,通过@Transactional就可以进行事务操作,更快捷而且简单,推荐使用,如下:
@Transactional
public void savePosts(PostsParam postsParam) {
// 保存文章
save(posts);
// 处理标签
insertOrUpdateTag(postsParam, posts);
}
1. Declarative Transaction Management (声明式事务管理)
声明式事务将事务管理代码从业务方法中抽离了出来,以声明式的方式来实现事务管理,对于开发者来说,声明式事务显然比编程式事务更易用、更好用。
当然了,要想实现事务管理和业务代码的抽离,就必须得用到 Spring 当中最关键最核心的技术之一,AOP,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。
声明式事务虽然优于编程式事务,但也有不足,声明式事务管理的粒度是方法级别,而编程式事务是可以精确到代码块级别的。
1.1 事务管理模型官方文档:https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#transaction-strategies
Spring 将事务管理的核心抽象为一个事务管理器(TransactionManager),它的源码只有一个简单的接口定义,属于一个标记接口:
public interface TransactionManager {
}
该接口有两个子接口,分别是编程式事务接口 ReactiveTransactionManager 和声明式事务接口 PlatformTransactionManager。我们来重点说说 PlatformTransactionManager,该接口定义了 3 个接口方法:
public interface PlatformTransactionManager extends TransactionManager{
// 根据事务定义获取事务状态
TransactionStatus getTransaction(TransactionDefinition definition)
throws TransactionException;
// 提交事务
void commit(TransactionStatus status) throws TransactionException;
// 事务回滚
void rollback(TransactionStatus status) throws TransactionException;
}
通过 PlatformTransactionManager 这个接口,Spring 为各个平台如 JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
参数 TransactionDefinition 和 @Transactional 注解是对应的,比如说 @Transactional 注解中定义的事务传播行为、隔离级别、事务超时时间、事务是否只读等属性,在 TransactionDefinition 都可以找得到。
TransactionDefinition.java 中提供的部分方法:
public interface TransactionDefinition {
// 事务的传播行为
default int getPropagationBehavior() {
return PROPAGATION_REQUIRED;
}
// 事务的隔离级别
default int getIsolationLevel() {
return ISOLATION_DEFAULT;
}
// 事务超时时间
default int getTimeout() {
return TIMEOUT_DEFAULT;
}
// 事务是否只读
default boolean isReadOnly() {
return false;
}
}
Transactional.java 中提供的部分方法:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
boolean readOnly() default false;
}
- @Transactional 注解中的 propagation 对应 TransactionDefinition 中的 getPropagationBehavior,默认值为 Propagation.REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED)。
- @Transactional 注解中的 isolation 对应 TransactionDefinition 中的 getIsolationLevel,默认值为 DEFAULT(TransactionDefinition.ISOLATION_DEFAULT)。
- @Transactional 注解中的 timeout 对应 TransactionDefinition 中的 getTimeout,默认值为TransactionDefinition.TIMEOUT_DEFAULT。
- @Transactional 注解中的 readOnly 对应 TransactionDefinition 中的 isReadOnly,默认值为 false。
官方文档:https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#tx-propagation
事务传播行为指当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。
声明式事务的传播行为可以通过 @Transactional 注解中的 propagation 属性来定义,比如说:
@Transactional(propagation = Propagation.REQUIRED)
public void savePosts(PostsParam postsParam) {
}
TransactionDefinition 一共定义了 7 种事务传播行为:
① PROPAGATION_REQUIRED:如果当前存在事务,就加入该事务,该设置是最常用的设置,如果当前没有事务,就创建一个新事务。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
}
调用methodA方法时,因为当前上下文不存在事务,所以会开启一个新的事务。当执行到methodB时,methodB发现当前上下文有事务,因此就加入到当前事务中来。
② PROPAGATION_SUPPORTS:如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
}
// 事务属性为SUPPORTS
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
}
单纯的调用methodB时,methodB方法是非事务的执行的。
当调用methdA时,methodB则加入了methodA的事务中,事务地执行。
③ PROPAGATION_MANDATORY:如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
}
// 事务属性为MANDATORY
@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
}
当调用methodA时,methodB则加入到methodA的事务中,事务地执行。
当单独调用methodB时,因为当前没有一个活动的事务,则会抛出异常。
④ PROPAGATION_REQUIRES_NEW:创建新事务,如果当前存在事务,则把当前事务挂起。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
}
// 事务属性为MANDATORY
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
}
如果methodA发生异常回滚,methodB不会跟着回滚,因为methodB开启了独立的事务。但是,如果methodB抛出了未被捕获的异常并且这个异常满足事务回滚规则的话,methodA同样也会回滚。
⑤ PROPAGATION_NESTED:如果当前存在事务,就在当前事务内执行;否则,就执行与 PROPAGATION_REQUIRED 类似的操作。
⑥ PROPAGATION_NEVER:总是以非事务方式执行,如果当前存在事务,则抛出异常。
⑦ PROPAGATION_NOT_SUPPORTED:总是非事务地执行,并挂起任何存在的事务。注意:使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作为事务管理器。
1.3 事务隔离级别前面我们已经了解了数据库的事务隔离级别,再来理解 Spring 的事务隔离级别就容易多了。
TransactionDefinition 中一共定义了 5 种事务隔离级别:
- ISOLATION_DEFAULT,使用数据库默认的隔离级别,MySql 默认采用的是 REPEATABLE_READ,也就是可重复读。
- ISOLATION_READ_UNCOMMITTED,最低的隔离级别,可能会出现脏读、幻读或者不可重复读。
- ISOLATION_READ_COMMITTED,允许读取并发事务提交的数据,可以防止脏读,但幻读和不可重复读仍然有可能发生。
- ISOLATION_REPEATABLE_READ,对同一字段的多次读取结果都是一致的,除非数据是被自身事务所修改的,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- ISOLATION_SERIALIZABLE,最高的隔离级别,虽然可以阻止脏读、幻读和不可重复读,但会严重影响程序性能。
事务超时,也就是指一个事务所允许执行的最长时间,如果在超时时间内还没有完成的话,就自动回滚。
假如事务的执行时间格外的长,由于事务涉及到对数据库的锁定,就会导致长时间运行的事务占用数据库资源。
1.5 事务的只读属性如果一个事务只是对数据库执行读操作,那么该数据库就可以利用事务的只读属性,采取优化措施,适用于多条数据库查询操作中。
为什么一个查询操作还要启用事务支持呢?
这是因为 MySql(innodb)默认对每一个连接都启用了 autocommit 模式,在该模式下,每一个发送到 MySql 服务器的 SQL 语句都会在一个单独的事务中进行处理,执行结束后会自动提交事务。
那如果我们给方法加上了 @Transactional 注解,那这个方法中所有的 SQL 都会放在一个事务里。否则,每条 SQL 都会单独开启一个事务,中间被其他事务修改了数据,都会实时读取到。
有些情况下,当一次执行多条查询语句时,需要保证数据一致性时,就需要启用事务支持。否则上一条 SQL 查询后,被其他用户改变了数据,那么下一个 SQL 查询可能就会出现不一致的状态。
1.6 事务回滚策略 2. Programmatic Transaction Management (编程式事务管理)官方文档:https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#tx-prog-template
Spring事务常见问题 1. 说一说Spring的事务机制- Spring事务底层是基于数据库事务和AOP机制的
- 首先对于使用了@Transactional注解的Bean, Spring会创建一个代理对象作为Bean
- 当调用代理对象的方法时,会先判断该方法上是否加了@Transactional注解
- 如果加了,那么则利用事务管理器创建一个数据库连接
- 并且修改数据库连接的autocommit属性为false,禁止此连接的自动提交,这是实现Spring事务非常重要的一步
- 然后执行当前方法,方法中会执行sql
- 执行完当前方法后,如果没有出现异常就直接提交事务
- 如果出现了异常,并且这个异常是需要回滚的就会回滚事务,否则仍然提交事务
- Spring事务的隔离级别对应的就是数据库的隔离级别
- Spring事务的传播机制是Spring事务自己实现的,也是Spridg事务中最复杂的
- Spring事务的传播机制是基于数据库连接来做的,一个数据库连接一个事务, 如果传播机制配置为需要新开一个事务, 那么实际上就是先建立一个数据库连接, 在此新数据库连接上执行sql。
| 参 数 名 称 | 功 能 描 述 |
|---|---|
| readOnly | 该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true) |
| rollbackFor | rollbackFor 该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如:指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class}) |
| rollbackForClassName | 该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如:指定单一异常类名称@Transactional(rollbackForClassName=”RuntimeException”)指定多个异常类名称:@Transactional(rollbackForClassName={“RuntimeException”,”Exception”}) |
| noRollbackFor | 该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。例如:指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class)指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class}) |
| noRollbackForClassName | 该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如:指定单一异常类名称:@Transactional(noRollbackForClassName=”RuntimeException”)指定多个异常类名称:@Transactional(noRollbackForClassName={“RuntimeException”,”Exception”}) |
| propagation | 该属性用于设置事务的传播行为。例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true) |
| isolation | 该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置 |
| timeout | 该属性用于设置事务的超时秒数,默认值为-1表示永不超时 事务超时设置: @Transactional(timeout=30) //默认是30秒 |
指示spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。
默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。
可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。
4. Spring事务失效场景参考文档:https://mp.weixin.qq.com/s/qoWlR4ohVMfZf8IlhdSQDQ
- 如果我们自定义的事务方法(即目标方法),它的访问权限不是public,而是private、default或protected的话,spring则不会提供事务功能。
- 事务方法使用final修饰,但是声明式事务的底层采用的是AOP,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,而添加事务功能。
- 数据库不支持事务
- 事务传播特性设置错误
- 开发者自己将异常处理了,但是没有抛出异常;一般如果需要对异常进行一些处理,如打印日志等,之后还是应该手动抛出一个能回滚的异常(spring事务,默认情况下只会回滚RuntimeException(运行时异常)和Error(错误))
1)第一类丢失更新:
在没有事务隔离的情况下,两个事务都同时更新一行数据,但是第二个事务却中途失败退出, 导致对数据的两个修改都失效了。
例如:
张三的工资为5000,事务A中获取工资为5000,事务B获取工资为5000,汇入100,并提交数据库,工资变为5100,随后事务A发生异常,回滚了,恢复张三的工资为5000,这样就导致事务B的更新丢失了。
2)脏读:
脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
例如:
张三的工资为5000,事务A中把他的工资改为8000,但事务A尚未提交。
与此同时,
事务B正在读取张三的工资,读取到张三的工资为8000。
随后,
事务A发生异常,而回滚了事务。张三的工资又回滚为5000。
最后,
事务B读取到的张三工资为8000的数据即为脏数据,事务B做了一次脏读。
3)不可重复读:
是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
例如:
在事务A中,读取到张三的工资为5000,操作没有完成,事务还没提交。
与此同时,
事务B把张三的工资改为8000,并提交了事务。
随后,
在事务A中,再次读取张三的工资,此时工资变为8000。在一个事务中前后两次读取的结果并不致,导致了不可重复读。
4)第二类丢失更新:
不可重复读的特例。
有两个并发事务同时读取同一行数据,然后其中一个对它进行修改提交,而另一个也进行了修改提交。这就会造成第一次写操作失效。
例如:
在事务A中,读取到张三的存款为5000,操作没有完成,事务还没提交。
与此同时,
事务B,存储1000,把张三的存款改为6000,并提交了事务。
随后,
在事务A中,存储500,把张三的存款改为5500,并提交了事务,这样事务A的更新覆盖了事务B的更新。
5)幻读:
是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。
例如:
目前工资为5000的员工有10人,事务A读取所有工资为5000的人数为10人。
此时,
事务B插入一条工资也为5000的记录。
这是,事务A再次读取工资为5000的员工,记录为11人。此时产生了幻读。
提醒:
不可重复读的重点是修改,同样的条件,你读取过的数据,再次读取出来发现值不一样了
幻读的重点在于新增或者删除,同样的条件,第 1 次和第 2 次读出来的记录数不一样



