# 1.什么是事务
- 数据库事务:是指作为单个逻辑工作单元执行的一系列操作,这些操作要么一起成功,要么一起失败,是一个不可分割的工作单元。
- 日常工作中,涉及到事务的场景非常多,一个 service 中往往需要调用不同的 dao 层方法,这些方法要么同时成功要么同时失败,我们需要在 service 层确保这一点。
# 2.事务四大特性
- 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。
- 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。
- 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括未提交读(Read Uncommitted)、提交读(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。
- 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
# 3.三大基础设施
三个核心类是 Spring 处理事务的核心类
# 1.PlatformTransactionManager
可以看到
PlatformTransactionManager
中定义了基本的事务操作方法,这些事务操作方法都是平台无关的,具体的实现都是由不同的子类来实现的。这句话是啥意思呢,简单点来说就像我们平常使用的jdbc一样,SUN 公司制定标准,其他数据库厂商提供具体的实现。你是用mysql或者是oracle规则是不一样的,这个事务也是这样的。如果你使用的是 JDBC 那么可以将 DataSourceTransactionManager
作为事务管理器;如果你使用的是 Hibernate,那么可以将 HibernateTransactionManager
作为事务管理器;
PlatformTransactionManager
主要有如下三个方法:
- getTransaction():getTransaction() 是根据传入的 TransactionDefinition 获取一个事务对象,TransactionDefinition 中定义了一些事务的基本规则,例如传播性、隔离级别等。
- commit():commit() 方法用来提交事务。
- rollback():rollback() 方法用来回滚事务。
# 2.TransactionDefinition
TransactionDefinition
用来描述事务的具体规则,也称作事务的属性。
- 五种属性:隔离性、传播性、回滚规则、超时时间、是否只读
可以看到一共有五个方法:
- 1.getIsolationLevel(),获取事务的隔离级别
- 2.getName(),获取事务的名称
- 3.getPropagationBehavior(),获取事务的传播性
- 4.getTimeout(),获取事务的超时时间
- 5.isReadOnly(),获取事务是否是只读事务
# 3.TransactionStatus
TransactionStatus 可以直接理解为事务本身
public interface TransactionStatus extends SavepointManager, Flushable {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
void flush();
boolean isCompleted();
}
- 1.isNewTransaction() 方法获取当前事务是否是一个新事务。
- 2.hasSavepoint() 方法判断是否存在 savePoint()。
- 3.setRollbackOnly() 方法设置事务必须回滚。
- 4.isRollbackOnly() 方法获取事务只能回滚。
- 5.flush() 方法将底层会话中的修改刷新到数据库,一般用于 Hibernate/JPA 的会话,对如 JDBC 类型的事务无任何影响。
- 6.isCompleted() 方法用来获取是一个事务是否结束。
# 4.俩种用法
- 编程式事务:
- 声明式事务:声明式事务如果使用
XML
配置,可以做到无侵入;如果使用Java
配置,也只有一个@Transactional
注解侵入而已,相对来说非常容易。 由于编程式事务在实际工作中用的比较少,本文所有案例主要是用声明式事务进行讲解
# 5.事务属性
MySQL 中有四种不同的隔离级别,这四种不同的隔离级别在 Spring 中都得到了很好的支持。Spring 中默认的事务隔离级别是 default,即数据库本身的隔离级别是啥就是啥,default 就能满足我们日常开发中的大部分场景。
在代码中我们可以通过@Transactional注解中的isloation进行配置
# 6.事务的传播性
何为传播性:事务传播行为是为了解决业务层方法之间互相调用的事务问题,当一个事务方法被另一个事务方法调用时,事务该以何种状态存在?例如新方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行,等等,这些规则就涉及到事务的传播性。
一共有七种传播特性。下面我们对这七种传播特性进行详细讲解
具体配置我们可以采用如上这种方法
为了方便进行测试 我们定义了三个方法 test(),test1(),test2(),test1(),test2()方法都是直接调用dao层对数据库进行更新操作的,test()方法直接调用test1和test2方法,数据库的结果就不给大家展示了,不同操作情况不同结果直接告诉大家。方法如下所示
REQUIRED(默认)如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
- 情况1 test()和test2()方法上@Transactional注解,告诉大家这种情况,无论是这三个方法哪一个方法出现了异常,都会进行回滚
- 情况2 只有test2()方法上有@Transactional 注解,这种情况,如果test2()方法出现了异常,只有它自己会回滚,test1不会进行回滚,因为它根本就没有注解,如果test方法或者test1方法出现了异常,test2也不会回滚,原理是因为我们都知道回滚靠的是捕获异常进行处理然后在回滚,这种情况下test2根本就捕获不到test或者test1的异常,所以跟不不会进行回滚操作,同理 如果只有test1和test2有@Transactional注解也是一样的道理,谁发生了异常谁回滚,test方法发生了异常都不会回滚。
REQUIRES_NEW:表示创建一个新的事务,如果当前存在事务,则把当前事务挂起。REQUIRES_NEW 都会开启自己的事务
- 情况1 test方法加上@Transactional但是还是使用的默认的传播特性,而test2加上@Transactional使用REQUIRES_NEW 传播特性。如果这个时候test1或者test方法出现异常告诉大家,test1方法会进行回滚,而test2方法不会进行回滚。现在我们来理解一下它的官方解释就懂了,test2方法之所以不会回滚是因为它把外面test方法的事务挂起了,重新开了一个自己的事务,也就是独立的事务,所以它感知不到外面的异常了,他就不会进行回滚。但如果test2方法出现了异常,则test2方法一定会回滚,但test1会不会回滚也分为俩种情况,第一种情况就是test2抛出的异常在test方法被处理掉了,什么叫被处理掉了,就是test2方法被trycatch了,这种情况test1不会回滚,原因就是因为test2的异常被处理了,test方法就感知不到异常了,所以就不会回滚,也就是因为这俩个事务不是同一个事务,但如果没有被try catch test1方法就会被回滚。
NESTED:表示如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 TransactionDefinition.PROPAGATION_REQUIRED
- 情况1:还是test方法加上@Transactional但是还是使用的默认的传播特性,而test2加上@Transactional使用NESTED 传播特性,如果test方法出现了异常,test1和test2都进行回滚,因为test2方法的事务变成了test方法的子事务了也就是对应如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行 官方解释的这个前半句话,后半句话就如上所示了。
MANDATORY:MANDATORY 表示如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- 情况1:还是test方法加上@Transactional但是还是使用的默认的传播特性,而test2@Transactional使用MANDATORY传播特性。就是无论哪个方法出现异常test1test2都会回滚,但要注意,如果test2加上@Transactional使用MANDATORY传播特性 但test方法没有使用@Transactional,调用的时候就会发生异常
SUPPORTS:表示如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- 这里理解起来就简单了,前半句话相信大家都已经理解了,后半句话的意思就是如果test方法没有加上@Transactional但是还是使用的默认的传播特性,而test2加上@Transactional使用SUPPORTS传播特性,这种情况 其实test2有没有这个注解都是一样的了。因为他回以非事务的方式继续运行。什么是非事务,就是加不加注解都一样,出现异常了也不会回滚。
NOT_SUPPORTED:NOT_SUPPORTED 表示以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- 这个特性就很有意思了,其实这个注解简单点来说,这个注解加没加区别不太大,就是无论哪个地方发生了异常它都不会回滚,哪怕它自己的方法里面出现了异常,他也不会回滚。
NEVER 表示以非事务方式运行,如果当前存在事务,则抛出异常。
- 这个特性的意思就是我就想不要事务,如果你给我带了事务,我就抛异常,比如test2方法上加上@Transactional使用NEVER传播特性,如果这个时候test方法加上@Transactional使用的默认的传播特性,就会抛出异常,因为我本身是不想要事务的,但现在你在外面给我带了一个事务进来,我就不是很开心。
# 7.回滚规则:
- 默认情况下,事务只有遇到运行期异常(RuntimeException 的子类)以及 Error 时才会回滚,在遇到检查型(Checked Exception)异常时不会回滚。像 1/0,空指针这些是 RuntimeException,而 IOException 则算是 Checked Exception,换言之,默认情况下,如果发生 IOException 并不会导致事务回滚。
- 如果我们希望发生 IOException 时也能触发事务回滚,那么可以按照如下方式配置:
@Transactional(rollbackFor = IOException.class)
public void handle2() {
jdbcTemplate.update("update user set money = ? where username=?;", 1, "zhangsan");
accountService.handle1();
}
- 我们也可以指定在发生某些异常时不回滚,例如当系统抛出 ArithmeticException 异常并不要触发事务回滚,
@Transactional(noRollbackFor = ArithmeticException.class)
public void handle2() {
jdbcTemplate.update("update user set money = ? where username=?;", 1, "zhangsan");
accountService.handle1();
}
# 8. 是否只读
- 只读事务一般设置在查询方法上,但不是所有的查询方法都需要只读事务,要看具体情况。 一般来说,如果这个业务方法只有一个查询 SQL,那么就没必要添加事务,强行添加最终效果适得其反。 但是如果一个业务方法中有多个查询 SQL,情况就不一样了:多个查询 SQL,默认情况下,每个查询 SQL 都会开启一个独立的事务,这样,如果有并发操作修改了数据,那么多个查询 SQL 就会查到不一样的数据。此时,如果我们开启事务,并设置为只读事务,那么多个查询 SQL 将被置于同一个事务中,多条相同的 SQL 在该事务中执行将会获取到相同的查询结果。
这时候大家可能会疑惑,这个和mysql隔离级别中的隔离性有什么区别。 这个里面我们指的是同一个方法,里面有多条查询的sql,因为高并发可能会出现多条sql查询结果不一样。是mysql当中的是不一样的,如果我们不加这个注解,就代表这个方法里每个查询sql都是一个独立的事务,而如果设置只读,则所有的sql就在同一个事务里。
# 9.超时时间
@Transactional(timeout = 10)
# 10.注意事项
- 事务只能应用到 public 方法上才会有效
- 务需要从外部调用,Spring 自调事务用会失效。即相同类里边,A 方法没有事务,B 方法有事务,A 方法调用 B 方法,则 B 方法的事务会失效,这点尤其要注意,因为代理模式只拦截通过代理传入的外部方法调用,所以自调用事务是不生效的。
- 建议事务注解 @Transactional 一般添加在实现类上,而不要定义在接口上,如果加在接口类或接口方法上时,只有配置基于接口的代理这个注解才会生效。
# 11 srping事务什么时候会失效
- bean对象没有被Spring容器管理
- 方法的访问修饰符不是public
- 自身调用问题
- 数据源没有配置事务管理器
- 数据库不支持事务
- 异常被捕获
- 异常类型错误或者配置错误