资讯

展开

Mybatis---Spring整合后回滚失效并且自动保存了?

作者:快盘下载 人气:

Mybatis - Spring整合后回滚失效并且自动保存了?

前言一. 案例回顾二. 案例分析2.1 从rollback函数开始找问题2.2 从 commit 事务提交去证实 三. 问题解决

前言

我当时在整理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文件;
Mybatis---Spring整合后回滚失效并且自动保存了?
我数据库中的数据;

此时我调用一下接口;
-
程序在跑到插入操作的时候;我们打个断点;
Spring整合后回滚失效并且自动保存了?
此时再看看数据库;
Mybatis
发现数据竟然直接插入了?这是什么鬼?

二. 案例分析

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();。结果我调试一下;一看发现;
Mybatis
这个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默认带的一个数据源。
Spring整合后回滚失效并且自动保存了?
而我们并没有在程序里面对HikariProxyConnection这个数据源做自动提交功能的设置。

同时我们可以看到程序的调用链里面存在着底层Connection的获取操作;
-
也就是说;

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

说白了;就是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;我个人理解为就是有脏数据可能的意思。
Spring整合后回滚失效并且自动保存了?

那么我们再看显式地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的提交。
Spring整合后回滚失效并且自动保存了?
而我们的SQL执行是依赖于executor的一个提交操作。而SpringManagedTransaction的事务;我觉得更倾向于一种整体的业务逻辑。executor则面向的是单个的SQL执行操作。只不过executor的最终实现就是SpringManagedTransaction的commit操作。因此案例的插入操作是能够成功的。

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

三. 问题解决

Spring配置文件中添加数据源自动提交属性的配置;
-
此时再进行测试;代码跑到插入操作之后。
Spring整合后回滚失效并且自动保存了?
此时数据库中的数据并没有生效;事务成功了。
Spring整合后回滚失效并且自动保存了?

加载全部内容

相关教程
猜你喜欢
用户评论
快盘暂不提供评论功能!