IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Mybatis - Spring整合后回滚失效并且自动保存了? -> 正文阅读

[Java知识库]Mybatis - Spring整合后回滚失效并且自动保存了?

前言

我当时在整理Mybatis的一个二级缓存问题:Mybatis - 单机器下二级缓存脏读问题的解决(TransactionalCache的运用)

当时写的项目案例中,我发现事务回滚无法失效,虽然结果上并不影响二级缓存的一个结论。但是这个问题一直困扰了我好久。最后看了源码才发现问题出在哪里。

一. 案例回顾

看下我的程序:

@PostMapping("/hello")
public User hello(@RequestBody User user) {
    SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) myApplicationContext.applicationContext.getBean("sqlSessionFactory");
    SqlSession sqlSession1 = sqlSessionFactory.openSession(false);
    SqlSession sqlSession2 = sqlSessionFactory.openSession(false);
    UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);

    User tom = mapper1.getUserById("tom");

    mapper1.insertUser(user);
    List<User> users = mapper1.getUsers();
    sqlSession1.rollback();

    List<User> ss = mapper2.getUsers();

    System.out.println("SqlSession1:" + users.size());
    System.out.println("SqlSession2:" + ss.size());

    return tom;
}

我们先不看这段代码有什么逻辑和意义,我们只关注sqlSession1.rollback();这段代码。理论上来说,如果这个代码回滚了,那么我们就应该把上面的insert操作也给回滚。但是实际上却不是这样。

这是我的application.yml文件:
在这里插入图片描述
我数据库中的数据:
在这里插入图片描述
此时我调用一下接口:
在这里插入图片描述
程序在跑到插入操作的时候,我们打个断点:
在这里插入图片描述
此时再看看数据库:
在这里插入图片描述
发现数据竟然直接插入了?这是什么鬼?

二. 案例分析

2.1 从rollback函数开始找问题

我这里是从rollback开始分析然后找到原因的:

sqlSession1.rollback();
↓↓↓↓↓
public class DefaultSqlSession implements SqlSession {
  @Override
  public void rollback(boolean force) {
    try {
      executor.rollback(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error rolling back transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
}
↓↓↓↓↓
public class CachingExecutor implements Executor {
  @Override
  public void rollback(boolean required) throws SQLException {
    try {
      delegate.rollback(required);
    } finally {
      if (required) {
        tcm.rollback();
      }
    }
  }
}
↓↓↓↓↓
public abstract class BaseExecutor implements Executor {
  @Override
  public void rollback(boolean required) throws SQLException {
    if (!closed) {
      try {
        clearLocalCache();
        flushStatements(true);
      } finally {
        if (required) {
          transaction.rollback();
        }
      }
    }
  }
}
↓↓↓↓↓
public class SpringManagedTransaction implements Transaction {
  @Override
  public void rollback() throws SQLException {
    if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
      LOGGER.debug(() -> "Rolling back JDBC Connection [" + this.connection + "]");
      this.connection.rollback();
    }
  }
}

结果发现,程序在进行if判断的时候,根本走不到this.connection.rollback();。结果我调试一下,一看发现:
在这里插入图片描述
这个this.autoCommittrue。可见,他并不是我们代码中对于SqlSessionautoCommit属性,两个是不一样的东西。

看一下它的引用:

private void openConnection() throws SQLException {
  this.connection = DataSourceUtils.getConnection(this.dataSource);
  this.autoCommit = this.connection.getAutoCommit();
  this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);

  LOGGER.debug(() -> "JDBC Connection [" + this.connection + "] will"
      + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring");
}

如图:发现这个connection的类型HikariProxyConnectionSpringBoot默认带的一个数据源。
在这里插入图片描述
而我们并没有在程序里面对HikariProxyConnection这个数据源做自动提交功能的设置。

同时我们可以看到程序的调用链里面存在着底层Connection的获取操作:
在这里插入图片描述
也就是说:

  1. Mybatis在整合Spring之后,其事务类型的类是SpringManagedTransaction
  2. 通过SpringManagedTransaction去拿到Connection链接的时候,SpringBoot默认的类型是HikariProxyConnection
  3. 因为HikariProxyConnection默认情况下autoCommit属性是true
  4. 因此案例中的代码,事务是自动提交的。

说白了,就是Mybatis整合Spring之后,自动提交属性是根据SpringManagedTransactionautoCommit属性。而不是sqlSessionFactory.openSession(false);

为了进一步证实这样的说法,我们再从commit去看这个问题

2.2 从 commit 事务提交去证实

首先我们看下sqlSessionFactory.openSession(false)这个函数有什么用:

public class DefaultSqlSessionFactory implements SqlSessionFactory {
  @Override
  public SqlSession openSession(boolean autoCommit) {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
  }

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      // 创建一个DefaultSqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
}

public class DefaultSqlSession implements SqlSession {
  private final boolean autoCommit;
  private boolean dirty;
  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }
}

可见最后就是创建了一个DefaultSqlSession对象实例,然后里面的dirty默认是true。并且autoCommit赋值为false。但是我们案例中有着insert操作,根据调用链,最后会执行到DefaultSqlSessionupdate函数,就是说明数据发生了更改,此时dirty赋值为true,我个人理解为就是有脏数据可能的意思。
在这里插入图片描述

那么我们再看显式地SqlSession.commit()操作:

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

可见isCommitOrRollbackRequired这个函数,对于当前SqlSessioncommit操作至关重要。他决定着当前SqlSession是否提交。也就是说我们的insert操作是否会成功。 再来看下它的源码:

// autoCommit我们在sqlSessionFactory.openSession(false)中设置为false
// dirty由于发生了数据的更改(增删改),改为true。
// force是传进来的默认值false
private boolean isCommitOrRollbackRequired(boolean force) {
  return (!autoCommit && dirty) || force;
}

那么在执行完insert操作后,Mybatis就会去判断当前SqlSession是否需要进行commit提交。而此时整个表达式的值是true,因此最后的执行结果会提交上去,最后同步到数据库。因此回滚功能就失效了。(因为自动提交了)。

最后从调用链来看,还需要注意一点的就是:

  • 先执行executor的一个事务提交,在执行SpringManagedTransaction的提交。
    在这里插入图片描述
    而我们的SQL执行是依赖于executor的一个提交操作。而SpringManagedTransaction的事务,我觉得更倾向于一种整体的业务逻辑。executor则面向的是单个的SQL执行操作。只不过executor的最终实现就是SpringManagedTransaction的commit操作。因此案例的插入操作是能够成功的。

因此,我们仅仅改变SqlSessionautoCommit属性是不够的,还需要改变数据源的autoCommit机制。 那么知道这个问题的本质之后,我们只需要做到一点:改变你项目中数据源的自动提交机制即可。

三. 问题解决

Spring配置文件中添加数据源自动提交属性的配置:
在这里插入图片描述
此时再进行测试:代码跑到插入操作之后。
在这里插入图片描述
此时数据库中的数据并没有生效:事务成功了。
在这里插入图片描述

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-10-08 20:27:04  更:2022-10-08 20:31:55 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年3日历 -2025/3/10 17:17:05-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码