Mybatis:颠覆你心中对事务的理解

本人免费整理了Java高级资料,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程,一共30G,需要自己领取。
传送门:https://mp.weixin.qq.com/s/osB-BOl6W-ZLTSttTkqMPQ

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

2、一说到事务,人们可能又会想起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()含义为关闭一个数据库连接,这已经不再是事务方法了。

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实例,那么代码也是没有问题的,双重支持。

事务工厂TransactionFactory

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

<transactionManager type="JDBC" />

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

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()等方法内部寻找有关事务的任何方法。

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

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。

原文地址:https://www.cnblogs.com/yunxi520/p/12534298.html

时间: 2024-10-12 22:13:07

Mybatis:颠覆你心中对事务的理解的相关文章

Mybatis:深入对事务的理解

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

事务的理解

数据库事务包含4个特性:原子性,一致性,隔离性,永久性. 原子性和一致性是紧密相关的. 隔离性是两个事务互不干扰,如果修改同一条记录,结果是什么样的?怎么控制结果的准确性. 事务的理解,布布扣,bubuko.com

spring mvc + mybatis + spring aop声明式事务管理没有作用

在最近的一个项目中,采用springMVC.mybatis,发现一个很恼人的问题:事务管理不起作用!!网上查阅了大量的资料,尝试了各种解决办法,亦未能解决问题! spring版本:3.0.5 mybatis版本:3.2.2 1.applicationContext.xml配置: mvc + mybatis + spring aop声明式事务管理没有作用" title="spring mvc + mybatis + spring aop声明式事务管理没有作用">2.spr

使用spring+mybatis+atomikos+tomcat构建分布式事务

本文通过一个demo,介绍如何使用spring+mybatis+atomikos+tomcat构建在一个事务中涉及两个数据源的web应用. demo功能:实现一个能成功提交和回滚的涉及两个数据库数据源的XA事务. demo将实现: 1.一次性在两个数据库的两张表中各插入一条数据并提交. 2.一次性在两个数据库的两张表中各插入一条数据并回滚. 测试方式:restful web api 使用工具: spring 4.1.1.RELEASE mybatis 3.2.7 atomikos 3.7.0 t

spring + myBatis 常见错误:注解事务不回滚

最近项目在用springMVC+spring+myBatis框架,在配置事务的时候发现一个事务不能回滚的问题. 刚开始配置如下:springMVC.xml配置内容: spring.xml配置内容 从上面两个配置文件看出,开始的时候我把Service配置在springMVC中的.但是,事务注解我配置在了spring中.这样就会出现问题了.因为spring的容器(applicationContext)和springMVC的(applicationContext)是不同的. spring容器加载得时候

基于spring+mybatis+atomikos+jta实现分布式事务(2)-动态切换数据源

本文介绍基于spring+mybatis+atomikos+jta实现分布式事务,由程序动态切换数据源,通过atomikos可实现分布式事务一致性. 版本:spring-3.2.9.RELEASE.mybatis-3.4.4.atomikos-4.0.5.jdk1.8 1,maven配置文件pom.xml如下: <!-- test --> <dependency> <groupId>junit</groupId> <artifactId>juni

事务的理解及应用

1.事物的理解: 事务(Transaction)是并发控制的基本单位.指作为单个逻辑工作单元执行的一系列操作,而这些逻辑工作单元需要具有原子性,一致性,隔离性和持久性四个属性,统称为ACID特性. Atomic(原子性):事务中的所有元素作为一个整体提交或回滚,事务的个元素是不可分的,事务是一个完整操作. Consistency(一致性):只有合法的数据可以被写入数据库,否则事务应该将其回滚到最初状态.事物完成时和事物开始之前,数据存储中的数据处于一致状态.保证数据的无损. Isolation(

SpringMVC+MyBatis+JMS+JTA(分布式事务)

SpringMVC+MyBatis 相信已经是现在企业开发中常用技术了. 因为一些需求,我们需要集成JMS(我使用的是ActiveMQ),大家应该都知道,MQ也可以认为是一个数据源,数据也是数据源.这种情况下,如果我们在一个方法内操作JMS和数据库,我们就需要保证这个方法执行需要满足原子性. 这也就意味这一个问题,我们要多个数据源在同一个事务中.这里不枚举市面上的所有解决方案,其实atomikos JTA 是一个比较不错分布式事务管理器. 当然如果没有使用到JMS,在需要多数据源(也就是需要连接

Spring+Mybatis+MySql+Maven 简单的事务管理案例

利用Maven来管理项目中的JAR包,同时使用Spring在业务处理层进行事务管理.数据库使用MySq,数据处理层使用Spring和Mybatis结合. 本案例代码主要结构如图: 1.数据库脚本 -- ---------------------------- -- Table structure for `user` -- ---------------------------- DROP TABLE IF EXISTS `user`; CREATE TABLE `user` (   `id`