首页 > 其他分享 >详解 MyBatis 事务管理,彻底颠覆你对事务的理解!

详解 MyBatis 事务管理,彻底颠覆你对事务的理解!

时间:2022-10-11 11:48:30浏览次数:60  
标签:事务管理 事务 rollback sqlSession 详解 student close MyBatis commit

来源:https://my.oschina.net/zudajun/blog/666764

前言

说到数据库事务,人们脑海里自然不自然的就会浮现出事务的四大特性、四大隔离级别、七大传播特性。四大还好说,问题是七大传播特性是哪儿来的?是 Spring 在当前线程内,处理多个数据库操作方法事务时所做的一种事务应用策略。事务本身并不存在什么传播特性,不要混淆事务本身和 Spring 的事务应用策略。(当然,找工作面试时,还是可以巧妙的描述传播特性的)

一说到事务,人们可能又会想起 create、begin、commit、rollback、close、suspend。可实际上,只有 commit、rollback 是实际存在的,剩下的 create、begin、close、suspend 都是虚幻的,是业务层或数据库底层应用语意,而非 JDBC 事务的真实命令。

create(事务创建):不存在。

begin(事务开始):姑且认为存在于 DB 的命令行中,比如 Mysql 的 start transaction 命令,以及其他数据库中的 begin transaction 命令。JDBC 中不存在。

close(事务关闭):不存在。应用程序接口中的 close () 方法,是为了把 connection 放回数据库连接池中,供下一次使用,与事务毫无关系。

suspend(事务挂起):不存在。Spring 中事务挂起的含义是,需要新事务时,将现有的 connection1 保存起来(它还有尚未提交的事务),然后创建 connection2,connection2 提交、回滚、关闭完毕后,再把 connection1 取出来,完成提交、回滚、关闭等动作,保存 connection1 的动作称之为事务挂起。在 JDBC 中,是根本不存在事务挂起的说法的,也不存在这样的接口方法。

因此,记住事务的三个真实存在的方法,不要被各种事务状态名词所迷惑,它们分别是:conn.setAutoCommit()、conn.commit()、conn.rollback()

conn.close () 含义为关闭一个数据库连接,这已经不再是事务方法了。


推荐一个开源免费的 Spring Boot 最全教程:

https://github.com/javastacks/spring-boot-best-practice

1. Mybaits 中的事务接口 Transaction

public interface Transaction {
    Connection getConnection() throws SQLException;
    void commit() throws SQLException;
    void rollback() throws SQLException;
    void close() throws SQLException;
}

有了文章开头的分析,当你再次看到 close () 方法时,千万别再认为是关闭一个事务了,而是关闭一个 conn 连接,或者是把 conn 连接放回连接池内。

事务类层次结构图:

JdbcTransaction:单独使用 Mybatis 时,默认的事务管理实现类,就和它的名字一样,它就是我们常说的 JDBC 事务的极简封装,和编程使用 mysql-connector-java-5.1.38-bin.jar 事务驱动没啥差别。其极简封装,仅是让 connection 支持连接池而已。

ManagedTransaction:含义为托管事务,空壳事务管理器,皮包公司。仅是提醒用户,在其它环境中应用时,把事务托管给其它框架,比如托管给 Spring,让 Spring 去管理事务。

org.apache.ibatis.transaction.jdbc.JdbcTransaction.java 部分源码。

@Override
  public void close() throws SQLException {
    if (connection != null) {
      resetAutoCommit();
      if (log.isDebugEnabled()) {
        log.debug("Closing JDBC Connection [" + connection + "]");
      }
      connection.close();
    }
  }

面对上面这段代码,我们不禁好奇,connection.close () 之前,居然调用了一个 resetAutoCommit (),含义为重置 autoCommit 属性值。connection.close () 含义为销毁 conn,既然要销毁 conn,为何还多此一举的调用一个 resetAutoCommit () 呢?消失之前多喝口水,真的没有必要。

其实,原因是这样的,connection.close () 不意味着真的要销毁 conn,而是要把 conn 放回连接池,供下一次使用,既然还要使用,自然就需要重置 AutoCommit 属性了。通过生成 connection 代理类,来实现重回连接池的功能。如果 connection 是普通的 Connection 实例,那么代码也是没有问题的,双重支持。

2. 事务工厂 TransactionFactory

顾名思义,一个生产 JdbcTransaction 实例,一个生产 ManagedTransaction 实例。两个毫无实际意义的工厂类,除了 new 之外,没有其他代码。

<transactionManager type="JDBC" />

mybatis-config.xml 配置文件内,可配置事务管理类型。

3. Transaction 的用法

无论是 SqlSession,还是 Executor,它们的事务方法,最终都指向了 Transaction 的事务方法,即都是由 Transaction 来完成事务提交、回滚的。

配一个简单的时序图。

代码样例:

public static void main(String[] args) {
    SqlSession sqlSession = MybatisSqlSessionFactory.openSession();
    try {
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);

        Student student = new Student();
        student.setName("yy");
        student.setEmail("[email protected]");
        student.setDob(new Date());
        student.setPhone(new PhoneNumber("123-2568-8947"));

        studentMapper.insertStudent(student);
        sqlSession.commit();
    } catch (Exception e) {
        sqlSession.rollback();
    } finally {
        sqlSession.close();
    }
}

注:Executor 在执行 insertStudent (student) 方法时,与事务的提交、回滚、关闭毫无瓜葛(方法内部不会提交、回滚事务),需要像上面的代码一样,手动显示调用 commit ()、rollback ()、close () 等方法。

因此,后续在分析到类似 insert ()、update () 等方法内部时,需要忘记事务的存在,不要试图在 insert () 等方法内部寻找有关事务的任何方法。

4. 你可能关心的有关事务的几种特殊场景表现(重要)

1. 一个 conn 生命周期内,可以存在无数多个事务。

// 执行了connection.setAutoCommit(false),并返回
SqlSession sqlSession = MybatisSqlSessionFactory.openSession();
try {
    StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);

    Student student = new Student();
    student.setName("yy");
    student.setEmail("[email protected]");
    student.setDob(new Date());
    student.setPhone(new PhoneNumber("123-2568-8947"));

    studentMapper.insertStudent(student);
    // 提交
    sqlSession.commit();

    studentMapper.insertStudent(student);
    // 多次提交
    sqlSession.commit();
} catch (Exception e) {
        // 回滚,只能回滚当前未提交的事务
    sqlSession.rollback();
} finally {
    sqlSession.close();
}

对于 JDBC 来说,autoCommit=false 时,是自动开启事务的,执行 commit () 后,该事务结束。以上代码正常情况下,开启了 2 个事务,向数据库插入了 2 条数据。JDBC 中不存在 Hibernate 中的 session 的概念,在 JDBC 中,insert 了几次,数据库就会有几条记录,切勿混淆。而 rollback (),只能回滚当前未提交的事务。

2. autoCommit=false,没有执行 commit (),仅执行 close (),会发生什么?

try {
    studentMapper.insertStudent(student);
} finally {
    sqlSession.close();
}

就像上面这样的代码,没有 commit (),固执的程序员总是好奇这样的特例。

insert 后,close 之前,如果数据库的事务隔离级别是 read uncommitted,那么,我们可以在数据库中查询到该条记录。

接着执行 sqlSession.close () 时,经过 SqlSession 的判断,决定执行 rollback () 操作,于是,事务回滚,数据库记录消失。

下面,我们看看 org.apache.ibatis.session.defaults.DefaultSqlSession.java 中的 close () 方法源码。

  @Override
  public void close() {
    try {
      executor.close(isCommitOrRollbackRequired(false));
      dirty = false;
    } finally {
      ErrorContext.instance().reset();
    }
  }

事务是否回滚,依靠 isCommitOrRollbackRequired (false) 方法来判断。

  private boolean isCommitOrRollbackRequired(boolean force) {
    return (!autoCommit && dirty) || force;
  }

在上面的条件判断中,!autoCommit=true(取反当然是 true 了),force=false,最终是否回滚事务,只有 dirty 参数了,dirty 含义为是否是脏数据。

  @Override
  public int insert(String statement, Object parameter) {
    return update(statement, parameter);
  }

  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

源码很明确,只要执行 update 操作,就设置 dirty=true。insert、delete 最终也是执行 update 操作。

只有在执行完 commit ()、rollback ()、close () 等方法后,才会再次设置 dirty=false。

  @Override
  public void commit(boolean force) {
    try {
      executor.commit(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

因此,得出结论:autoCommit=false,但是没有手动 commit,在 sqlSession.close () 时,Mybatis 会将事务进行 rollback () 操作,然后才执行 conn.close () 关闭连接,当然数据最终也就没能持久化到数据库中了。

3. autoCommit=false,没有 commit,也没有 close,会发生什么?

studentMapper.insertStudent(student);

干脆,就这一句话,即不 commit,也不 close。

结论:insert 后,jvm 结束前,如果事务隔离级别是 read uncommitted,我们可以查到该条记录。jvm 结束后,事务被 rollback (),记录消失。通过断点 debug 方式,你可以看到效果。

这说明 JDBC 驱动实现,已经 Kao 虑到这样的特例情况,底层已经有相应的处理机制了。这也超出了我们的探究范围。

但是,一万个屌丝程序员会对你说:Don't do it like this. Go right way。

警告:请按正确的 try-catch-finally 编程方式处理事务,若不从,本人概不负责后果。

注:无参的 openSession () 方法,会自动设置 autoCommit=false。

总结:Mybatis 的 JdbcTransaction,和纯粹的 Jdbc 事务,几乎没有差别,它仅是扩展支持了连接池的 connection。另外,需要明确,无论你是否手动处理了事务,只要是对数据库进行任何 update 操作(update、delete、insert),都一定是在事务中进行的,这是数据库的设计规范之一。读完本篇文章,是否颠覆了你心中目前对事务的理解呢?欢迎点评。

近期热文推荐:

1.1,000+ 道 Java面试题及答案整理(2022最新版)

2.劲爆!Java 协程要来了。。。

3.Spring Boot 2.x 教程,太全了!

4.别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!

5.《Java开发手册(嵩山版)》最新发布,速速下载!

觉得不错,别忘了随手点赞+转发哦!

标签:事务管理,事务,rollback,sqlSession,详解,student,close,MyBatis,commit
From: https://www.cnblogs.com/javastack/p/16778672.html

相关文章

  • MyBatis
    Mybatis作用是一款优秀的持久层框架支持定制化的sql存储过程以及高级映射之前我们学过的所有的JDBC代码和手动设置参数获取结果集都不用写了使用简单的XML配......
  • 算法基础(五)| 差分算法及模板详解
    ⭐写在前面的话:本系列文章旨在复习算法刷题中常用的基础算法与数据结构,配以详细的图例解释,总结相应的代码模板,同时结合例题以达到最佳的学习效果。本专栏面向算法零基础但有......
  • springboot~对mybatis的start包进行单元测试
    一个start包,它不需要有springboot启动类,它只提供一切公用的功能,被其它包依赖就行了,通过META-INF/spring.factories或者META-INF/spring/org.springframework.boot.autoconf......
  • BUUCTF [NewStarCTF] Week1 WEB NotPHP 详解
    NotPHP<?phperror_reporting(0);highlight_file(__FILE__);if(file_get_contents($_GET['data'])=="WelcometoCTF"){if(md5($_GET['key1'])===md5($_GET['k......
  • linux如何挂载硬盘linux服务器上挂载磁盘(图文详解)
    1、检查网站的磁盘状态,确认是否有没有分区的磁盘。fdisk-l如上图所示,这个服务器有两个硬盘第一个42.9G,第二个236.2G。这种情况说明硬盘已经分区。2、格式化分区mkfs......
  • logback简介及logback.xml配置详解
    一、logback的介绍Logback是由log4j创始人设计的另一个开源日志组件,官方网站:http://logback.qos.ch。它当前分为下面下个模块:logback-core:其它两个模块的基础模块log......
  • Spring-04:持久化数据,了解SpringDataJPA和Mybatis
    1Javaweb阶段的方式在之前的Javaweb项目中,我们持久化数据的方式还是直接使用JDBC参考:Javaweb总结-目前开发Javaweb的套路梳理https://www.cnblogs.com/fancy2022/p/16......
  • linux sed 命令详解
     sed 常用选项 和 常用命令 sed是一个很好的文件处理工具,本身是一个管道命令,主要是 以行为单位 进行处理,可以将数据行进行替换、删除、新增、选取等特定工作。  s......
  • linux wc命令参数及用法详解
    Linux系统提供了wc命令来统计文件的行数统计当前目录下的所有文件行数:wc-l*当前目录以及子目录的所有文件行数:find .*|xargswc-l可以把*改成所要匹配的文件,例如Jav......
  • tar 命令详解
    tar命令[root@linux~]#tar[-cxtzjvfpPN]文件与目录....Usage:tar[OPTION...][FILE]...Examples:    tar-cfarchive.tarfoobar     #Crea......