Spring事务那些事儿

(一)事务的隔离级别

  • 大家都知道事务有四个属性,即ACID(原子性、一致性、隔离性、持久性)。这四个里面稍微难理解点的是一致性和持久性。所谓的一致性是指:事务执行前后数据的一致性状态,例如事务执行前用户有1万元,事务回滚后用户仍应该有1万元。而这里的持久性指的是:事务在提交后结果是永久的,即使程序崩溃数据也能恢复,当然数据库的持久性指的是高可靠性,事务执行后数据保证一定写到了磁盘及备份日志,但并不是指高可用性 ,如果你强行把机器的磁盘烧了,那数据也是无法恢复的。
  • 在事务隔离性的基础上,我们对事务间的相互影响程度进行定级,即事务的隔离级别。通常事务有四种隔离级别:read uncommitted、read committed、repeatable read、serializable。
    (1)read uncommitted是最低的隔离级别,其实就如其英文名一样,处于这个级别的事务可能会读到其他事务尚未提交的数据修改,即存在脏读问题。除此之外,还存在不可重复读和幻读问题问题。
    (2)read committed是大部分数据库的默认隔离级别,它解决了脏读问题,但是仍存在不可重复读和幻读问题。
    (3)repeatable read解决了不可重复读的问题,但是仍可能存在幻读的问题。值得一提的是mysql的默认级别是repeatable read,但它却不存在幻读问题,因为mysql通过使用next-key lock技术解决了问题,即使在repeatable read级别也能完全保证事务的隔离性要求。
    (4) serializable是最为严格的隔离级别,所有事务顺序执行,实际上是通过将并行转为串行来解决事务隔离问题,无疑会降低性能。

不可重复读和幻读的区别:
不可重复读指在同一事务中多次查询同一记录(eg:select * from xx where id = 1),查询结果不一致。这主要是由于多次读期间其他事务update或delete了记录。
幻读是指在同一事务中多次进行范围查询(eg:select count(*) from xx where id > 1 and id < 100),查询结果不一致。这主要是由于多次范围查询期间其他事务insert了新数据。

(二)事务的参与者及分类

在一般的事务场景中通常有以下几个参与者:

  1. RM:用于管理系统数据资源,即通常意义上的数据库服务器等。
  2. TP monitor:主要用于分布式事务,用来协调处理多个RM的事务处理。
  3. TM:事务的管理者,负责事务界定、事务上下文传播等功能。
  4. Applacation:运行于容器中的应用程序,例如spring应用程序。

我们通常会根据RM的数量将事务分为分布式事务和局部事务(本地事务)两种。 在分布式事务中通常会存在多个不同的RM,这些RM分布在不同的系统中,相互之间通过TP monitor协调,利用两阶段提交来保障事务的ACID属性。在局部事务中一般每次数据操作只有一个RM,数据操作只需操作一个数据库,在同一个事务中不会进行多个数据库的更新。接下来我们将着重介绍局部事务的实现,毕竟分布式事务我懂的也不多😜。

(三)局部事务详解

1.事务原理简介

首先我们需要了解的很重要的一点是,spring的事务底层实现本质上是依赖于其使用的数据库服务器,例如mysql数据库。在mysql数据库中事务的隔离依赖于mysql的锁机制,持久性和原子依赖于redo日志,而一致性依赖于undo日志。mysql提供事务功能的具体实现,spring负责界定事务的边界及定义事务的传播性,然后通过特定的数据访问技术(例:jdbc、hibernate等)的API对事务进行管理。
我们以jdbc举个简单的例子,我们可以看下面的代码:

        Connection connection = null;
        boolean rollback = false;
        try {
            connection = dataSource.getConnection();
            connection.setAutoCommit(false);
            // 数访问
            XXXXXXXXXXXX;
            connection.commit();
        } catch (SqlException e) {
            rollback = true;
        } finally {
            if (rollback) {
                connection.rollback();
            } else {
                connection.close();
            }
        }

这就是我们利用jdbc进行的最简单的事务管理,将autoCommit置为false,然后根据各种情况进行事务的回滚提交等操作。而我们平时普通的数据操作都是默认自动提交,mysql会自动隐式的帮我们commit事务。
但是上面的写法在实际的开发中存在很多的问题:1.不同的数数据访问技术有不同的api、2.我们需要自己手动捕获处理各种以sql异常、3.事务管理代码和数据访问代码及业务代码耦合在一起。

2.spring事务框架重要类

为了解决上面提到的问题,spring为我们提供了优秀的事务框架。我们先来介绍下事务框架中重要的类:

顶层接口 职责
TransactionDefinition 用来定义事务属性,包括事务的隔离性、传播性、超时时间、是否只读等属性。
TransactionStatus 用来记录事务开启到事务结束期间事务的状态,这个类一般在编程式事务中使用。
PlatformTransactionManager spring事务框架的核心类,负责事务的管理,包括事务的commit、rollback等。
3.spring事务传播性
事务传播性 说明
REQUIRED 业务方法需要在一个事务中运行,如果方法运行时,已处在一个事务中,那么就加入该事务,否则自己创建一个新的事务,这是spring默认的传播行为。
SUPPORTS 如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分,如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行。
MANDATORY 只能在一个已存在事务中执行,业务方法不能发起自己的事务,如果业务方法在没有事务的环境下调用,就抛异常。
REQUIRES_NEW 业务方法总是会为自己发起一个新的事务,如果方法已运行在一个事务中,则原有事务被挂起,新的事务被创建,直到方法结束,新事务才结束,原先的事务才会恢复执行。
NOT_SUPPORTED 声明方法需要事务,如果方法没有关联到一个事务,容器不会为它开启事务。如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行。
NEVER 声明方法绝对不能在事务范围内执行,如果方法在某个事务范围内执行,容器就抛异常。只有没关联到事务,才正常执行。
NESTED 如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动的事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保证点。内部事务回滚不会对外部事务造成影响, 它只对DataSourceTransactionManager 事务管理器起效。

--------未完待续,敬请期待

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章