数据库事务及Spring事物管理知识点

1、事务

1.1 事务的含义

事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消。也就是事务具有原子性,事务是逻辑上的一组操作,要么都执行,要么都不执行。

1.2 事务的四大特性

在这里插入图片描述
数据库事务四大特性简称为ACID:

  • 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
  • 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态,一致状态的含义是数据库中的数据应满足完整性约束。比如A账户给B账户转账,转账的事务执行前后A和B账户的金额总和应该是不变的。
  • 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
  • 持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。即使执行完成后断电了,重启之后数据也应该存在数据库中。

1.3 并发事务的问题

  • 脏读(Dirty read): 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。简言之就是一个事务读到另一个事务未提交数据。
  • 不可重复读(Unrepeatableread): 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。即一个事务读到另一个事务已经提交数据(这里强调的是更新或者删除操作)导致一个事务多次查询结果不一致。
  • 幻读(Phantom read): 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。即一个事务读到另一个事务已经提交数据(这里强调的是插入操作)导致一个事务多次查询结果不一致。

1.4 事务的隔离级别

隔离级别 解决问题
Serializable (串行化) 可避免脏读、不可重复读、幻读的发生。
Repeatable read (可重复读) 可避免脏读、不可重复读的发生。
Read committed (读已提交) 可避免脏读的发生。
Read uncommitted (读未提交) 脏读、不可重复读、幻读都可能发生。

以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然级别越高,执行效率就越低。像Serializable这样的级别,就是以锁表的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。在MySQL数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读);而在Oracle数据库中,只支持Serializable (串行化)级别和Read committed (读已提交)这两种级别,其中默认的为Read committed级别。

2、Spring事物管理

2.1 TransactionDefinition 接口中定义了隔离级别常量

常量名称 说明
ISOLATION_DEFAULT 使用数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别
ISOLATION_READ_UNCOMMITTED 最低的隔离级别,允许读取尚未提交的数据变更
ISOLATION_READ_COMMITTED 允许读取并发事务已经提交的数据
ISOLATION_REPEATABLE_READ 可以阻止脏读和不可重复读,但幻读仍有可能发生。
ISOLATION_SERIALIZABLE 最高的隔离级别,完全服从ACID的隔离级别

2.2 TransactionDefinition 接口中定义了传播行为常量

支持当前事务的情况:

常量名称 说明
PROPAGATION_REQUIRED 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
PROPAGATION_SUPPORTS 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
PROPAGATION_MANDATORY 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

不支持当前事务的情况:

常量名称 说明
PROPAGATION_REQUIRES_NEW 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
PROPAGATION_NEVER 以非事务方式运行,如果当前存在事务,则抛出异常。

特殊的情况:

常量名称 说明
PROPAGATION_NESTED 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED即新建一个独立事务。

PROPAGATION_NESTED 是 Spring 所特有的。以 PROPAGATION_NESTED 启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。如果熟悉 JDBC 中的保存点(SavePoint)的概念,那嵌套事务就很容易理解了,其实嵌套嵌套是子事务套在父事务中执行,子事务是父事务的一部分,在进入子事务之前,父事务建立一个回滚点,叫save point,然后执行子事务,这个子事务的执行也算是父事务的一部分,然后子事务执行结束,父事务继续执行。重点就在于那个save point。1)如果子事务回滚.,父事务会回滚到进入子事务前建立的save point,然后尝试其他的事务或者其他的业务逻辑,父事务之前的操作不会受到影响,更不会自动回滚。2)如果父事务回滚,子事务也会跟着回滚!为什么呢,因为父事务结束之前,子事务是不会提交的,我们说子事务是父事务的一部分,正是这个道理。3)事务的提交是子事务先提交,父事务再提交,子事务是父事务的一部分,由父事务统一提交。

2.3 Spring支持两种事务管理的方式

  • 编程式事务管理: 通过Transaction Template手动管理事务,实际应用中很少使用。
  • 使用XML配置声明式事务: 推荐使用(代码侵入性最小),实际是通过AOP实现。

实现声明式事务的三种方式:

  • 1.基于TransactionProxyFactoryBean的方式(很少使用)
    需要为每个事务管理的类配置一个TransactionProxyFactoryBean进行管理。使用时还需要在类中注入该代理类。
  • 2.基于AspectJ的方式(常使用)
    配置好之后,按照方法的名字进行管理,无需再类中添加任何东西。
  • 3.基于注解的方式(经常使用)
    配置简单,在业务层类上添加注解@Transactional。

注: 只要是以代理方式实现的声明式事务,无论是JDK动态代理,还是CGLIB直接写字节码生成代理,都只有public方法上的事务注解才起作用。而且必须在代理类外部调用才行,如果直接在目标类里面调用,事务照样不起作用(比如同一个类的方法A没有开启事务调用有事务的方法B,这个时候B的事务是无效的)。

发布了105 篇原创文章 · 获赞 7 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_43792385/article/details/102482127