关于java:详解-MyBatis-事务管理彻底颠覆你对事务的理解

4次阅读

共计 6110 个字符,预计需要花费 16 分钟才能阅读完成。

起源:https://my.oschina.net/zudaju…

前言

说到数据库事务,人们脑海里天然不天然的就会浮现出事务的四大个性、四大隔离级别、七大流传个性。四大还好说,问题是七大流传个性是哪儿来的?是 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 开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞 + 转发哦!

正文完
 0