概念
一个事务包含两个及两个以上的步骤
Spring是没法提供事务的,Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。
事务管理两种方式
1.声明式事务
不需要写逻辑,写一些配置文件就行了,通过XML配置,声明某方法的事务特征。或者通过注解,声明某方法的事务特征。
声明式事务易用性更高,对业务代码没有侵入性,耦合度低,易于维护,因此这种方式也是我们最常用的事务管理方式
2.编程式事务
是通过编写代码实现的事务管理。编程式事务的开始和结束完全取决于我们的需求,但这种方式存在一个致命的缺点,那就是事务规则与业务代码耦合度高,难以维护,因此我们很少使用这种方式对事务进行管理。
通常,spring是声明式事务管理:这种方式意味着你可以将事务管理和业务代码分离。底层是AOP技术,你只需要通过注解@Transactional或者XML配置管理事务。
事务的特性(ACID)
- 原子性(Atomicity): 一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。
- 一致性(Consistency): 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。
- 隔离性(Isolation): 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括未提交读(Read uncommitted)、提交读(read committed)、可重复读(repeatable read)和串行化(Serializable)。
- 持久性(Durability): 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
Spring如何添加事务
注解@Transactional
事务的隔离
了解事务的隔离之前,可以先看看如果没有隔离可能会出现的一些常见的并发异常。
常见的并发异常
第一类丢失更新
某一个事务的回滚,导致另外一个事务已更新的数据丢失了。
第二类丢失更新
某一个事务的提交,导致另外一个事务已更新的数据丢失了。
脏读
某一个事务,读取了另外一个事务未提交的数据。
不可重复读
某一个事务,对同一个数据前后读取的结果不一致。
幻读
某一个事务,对同一个表前后查询到的行数不一致。
和不可重复度比较像,只不过这个是查询到的行数不同,也就是查询了多行数据导致的不一致。
隔离级别
@Transactional(isolation = Isolation.READ_COMMITTED)
public void myTransactionalMethod() {
// 你的事务代码
}
事务隔离又分为 5 种不同的级别,数据库有的是后面4个,Spring自己又加了一个默认隔离级别(DEFAULT)
- 默认隔离级别(DEFAULT):用数据库默认的隔离级别,通常是数据库厂商的默认设置。不同的数据库系统可能有不同的默认级别。
- 未提交读(Read uncommitted),最低的隔离级别,允许“脏读”(dirty reads),事务可以看到其他事务“尚未提交”的修改。如果另一个事务回滚,那么当前事务读到的数据就是脏数据。
- 提交读(read committed),一个事务可能会遇到不可重复读(Non Repeatable Read)的问题。不可重复读是指,在一个事务内,多次读同一数据,在这个事务还没有结束时,如果另一个事务恰好修改了这个数据,那么,在第一个事务中,两次读取的数据就可能不一致。
- 可重复读(repeatable read),一个事务可能会遇到幻读(Phantom Read)的问题。幻读是指,在一个事务中,第一次查询某条记录,发现没有,但是,当试图更新这条不存在的记录时,竟然能成功,并且,再次读取同一条记录,它就神奇地出现了。
- 串行化(Serializable),最严格的隔离级别,所有事务按照次序依次执行,因此,脏读、不可重复读、幻读都不会出现。虽然 Serializable 隔离级别下的事务具有最高的安全性,但是,由于事务是串行执行,所以效率会大大下降,应用程序的性能会急剧降低。如果没有特别重要的情景,一般都不会使用 Serializable 隔离级别。
事务的传播机制
就是解决两个事务之间的交叉问题,例如事务A中调用了事务B,那怎么处理?事务传播机制定义了一个事务方法如何与嵌套的事务方法互动以及如何处理事务边界。
@Transactional(propagation = Propagation.REQUIRED)
public void myTransactionalMethod() {
// 你的事务代码
}
常用的是1、4、7
- REQUIRED(默认)如果当前没有事务,就新建一个事务,如果已经存在一个事务中,就加入到这个事务中。这是最常用的传播行为,也是默认值。如果外部方法有事务,那么内部方法将在该事务中运行。如果外部方法没有事务,内部方法将创建一个新的事务。
- SUPPORTS(支持)如果当前没有事务,就以非事务方式执行;如果已经存在一个事务,就加入到这个事务中。内部方法不会自己创建事务,只有在外部方法已经有事务的情况下才会加入。
- MANDATORY(强制)必须在一个已有的事务中执行,否则抛出异常。如果外部方法没有事务,那么调用内部方法将抛出异常。
- REQUIRES_NEW(新建)内部方法将始终在自己的事务中执行,会挂起外部方法的事务(如果有的话)。如果内部方法抛出异常,只会影响内部方法的事务,而不会影响外部方法的事务。
- NOT_SUPPORTED(不支持)内部方法将以非事务方式执行,如果外部方法有事务,那么外部事务会被挂起。这样,内部方法完全独立于外部事务。
- NEVER(绝不)内部方法必须在没有事务的情况下执行,如果外部方法有事务,将抛出异常。
- NESTED(嵌套)内部方法在一个嵌套的事务中执行,这个嵌套事务是外部事务的一部分。如果内部方法抛出异常,只会回滚内部事务,不会回滚外部事务。只有外部事务成功提交后,内部事务的提交才会生效。