乐趣区

关于后端:第08篇Mybatis事务处理

一、Jdk 底层实现

Java JDK 中提供了标准接口 Connection, 不同的数据库驱动负责具体的实现。前面无论是Spring 还是 Mybatis 对事务的解决,无论怎么的封装, 最究竟其到底都是由 Connection 来提供的能力。

public interface Connection  extends Wrapper, AutoCloseable {Statement createStatement() throws SQLException;
    void commit() throws SQLException;
    void rollback() throws SQLException;}

例如 com.mysql.cj.jdbc.ConnectionImpl。具体负责跟 mysql 进行通信执行命令。

二、Mybatis 实现

首先咱们来看 Mybatis 是如何对 Connection 进行事务的封装。首先咱们先来看一个图。

2.1 调用流程

依据下面的图咱们看, 都是一层一层的封装进行委派最终由 Connection 的具体数据库驱动来进行实现的。

  • SqlSession
  • Executor
  • Transaction
public interface SqlSession extends Closeable {void commit();
  void rollback();}
public interface Executor {void commit();
  void rollback();}
public interface Transaction {void commit() throws SQLException;
  void rollback() throws SQLException;}

2.2 实现原理

Mybatis 中咱们的接口是应用代理进行跟数据库进行交互的。所以他的事务提交逻辑是嵌套在代理办法中的。
通过后面的调用流程学习, 第 04 篇:Mybatis 代理对象生成咱们晓得最终都是在 MapperMethod 对 SqlSession 的调用执行数据库操作的。
而 SqlSession 是有两个包装类的。

  • SqlSession 通过底层的封装提供具体的调用指令
  • SqlSessionManager 对 SqlSession 进行代理, 主动对事务进行解决
  • SqlSessionTemplate 事务的解决齐全外包给 Spring 来解决

上面咱们别离来看下每个类具体都做了什么吧。

SqlSessionManager

SqlSessionManager 是对 SqlSession 的一个包装, 它会本人来治理 SqlSession。他的具体实现是通过对 SqlSession
生成代理,代理拦挡每个办法进行加强。


  private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
    this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(),
        new Class[]{SqlSession.class},
        new SqlSessionInterceptor());
  }

SqlSessionInterceptor

 private class SqlSessionInterceptor implements InvocationHandler {public SqlSessionInterceptor() {// Prevent Synthetic Access}

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
      if (sqlSession != null) {
        try {return method.invoke(sqlSession, args);
        } catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);
        }
      } else {try (SqlSession autoSqlSession = openSession()) {
          try {final Object result = method.invoke(autoSqlSession, args);
            autoSqlSession.commit();
            return result;
          } catch (Throwable t) {autoSqlSession.rollback();
            throw ExceptionUtil.unwrapThrowable(t);
          }
        }
      }
    }
  }
  1. 从 ThreadLocal 中获取 SqlSession,如果有,阐明是调用方要本人处理事务,那么就只进行执行数据库操作,不进行事务处理和连贯的敞开。
  2. 如果没有, 阐明要本人来治理事务,那么就新生成 SqlSession,帮咱们调用 SqlSession#commit 来提交事务, 失败进行回滚。

依据其中原理咱们晓得有两种应用方法,

  • 首先第一种本人治理 SqlSession 的形式
    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    // 实例化 sqlSessionManager
    SqlSessionManager sqlSessionManager = SqlSessionManager.newInstance(inputStream);
    // 第一步: 开启治理 SqlSession,创立一个 SqlSession 并存入到 ThreadLocal 中
    sqlSessionManager.startManagedSession();
    // 应用
    UserMapper mapper = sqlSessionManager.getMapper(UserMapper.class);
    mapper.save(new User("孙悟空"));
    // 第二步: 因为事务是咱们本人开启的, 所以要本人来操作提交事务,或者回滚
    sqlSessionManager.commit();
    // 第三步: 敞开连贯
    sqlSessionManager.close();
  • 第二种, 主动治理 SqlSession
    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    // 实例化 sqlSessionManager
    SqlSessionManager sqlSessionManager = SqlSessionManager.newInstance(inputStream);
    UserMapper mapper = sqlSessionManager.getMapper(UserMapper.class);
    mapper.save(new User("孙悟空"));
    // 只用关怀敞开就好了,事务的信息, 都帮咱们实现了。sqlSessionManager.close();

SqlSessionTemplate

线程平安、Spring 治理、与 Spring 事务管理一起应用的 SqlSession,以确保理论应用的 SqlSession 是与以后 Spring 事务关联的那个。此外,它还治理会话生命周期,包含依据 Spring 事务配置依据须要敞开、提交或回滚会话。
模板须要一个 SqlSessionFactory 来创立 SqlSession,作为构造函数参数传递。也能够结构批示要应用的执行器类型,如果没有,将应用会话工厂中定义的默认执行器类型。
此模板将 MyBatis PersistenceExceptions 转换为未经查看的 DataAccessExceptions,默认状况下应用 MyBatisExceptionTranslator。

==SqlSessionTemplate== 和 ==SqlSessionManager==

  • 相同点: 都是通过对 SqlSession 进行代理对办法进行加强的
  • 不同点: 前者是将 SqlSession 外包给 Spring 进行治理的, 后者是本人通过 ThreadLocal 进行治理的。

上面咱们来具体看下是如何拦挡加强的。

  1. 第一个点获取 SqlSession 不同。

    • 从 Spring 中的事务管理器中获取以后线程的事务信息
  2. 第二个点办法执行实现后都会主动敞开 SqlSession 或缩小援用

    • 为解决嵌套事务的状况, 每次执行完后会缩小一次援用。当援用都缩小为 0 才会真正进行敞开。
  3. 第三个点是否提交事务,有断定规定。

    • 只有 Spring 事务管理器中没有事务时候才会本人进行提交, 否则都外包给 Spring 进行治理。

上面咱们具体来看下代码的实现吧。

 private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator
              .translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {unwrapped = translated;}
        }
        throw unwrapped;
      } finally {if (sqlSession != null) {closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

getSqlSession

  • 从 Spring 提供的事务管理器 (TransactionSynchronizationManager) 中获取以后线程领有的 SqlSession
  • 如果没有就新建一个并注册到 TransactionSynchronizationManager 上。
 public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
    // 从 Spring 提供的事务管理器 (TransactionSynchronizationManager) 中获取以后线程领有的 SqlSession
    // 逻辑很简略 key=SqlSessionFactory value=SqlSessionHolder
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {return session;}

    LOGGER.debug(() -> "Creating a new SqlSession");
    session = sessionFactory.openSession(executorType);
    // 如果没有就新建一个并注册到 TransactionSynchronizationManager 上。registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }

registerSessionHolder

  • 为了保险先判断下以后线程中是否曾经存在同步器, 如果存在还注册就提醒: “SqlSession [” + session + “] was not registered for synchronization because synchronization is not active”);
  • 如果以后线程没有, 判断事务管理器是否是 SpringManagedTransactionFactory, 如果是就注册一个。
  • SqlSessionHolder#requested() 留神这一行, 创立后给援用次数加 1.
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
    SqlSessionHolder holder;
    if (TransactionSynchronizationManager.isSynchronizationActive()) {Environment environment = sessionFactory.getConfiguration().getEnvironment();

      if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]");

        holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
        TransactionSynchronizationManager.bindResource(sessionFactory, holder);
        TransactionSynchronizationManager
            .registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
        holder.setSynchronizedWithTransaction(true);
        holder.requested();} else {if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {LOGGER.debug(() -> "SqlSession [" + session
              + "] was not registered for synchronization because DataSource is not transactional");
        } else {
          throw new TransientDataAccessResourceException("SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
        }
      }
    } else {LOGGER.debug(() -> "SqlSession [" + session
          + "] was not registered for synchronization because synchronization is not active");
    }

closeSqlSession

  • 如果是 Spring 的事务管理,就缩小援用
  • 如果不是 Spring 的事务管理, 就间接敞开
  public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {notNull(session, NO_SQL_SESSION_SPECIFIED);
    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);

    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    if ((holder != null) && (holder.getSqlSession() == session)) {LOGGER.debug(() -> "Releasing transactional SqlSession [" + session + "]");
      holder.released();} else {LOGGER.debug(() -> "Closing non transactional SqlSession [" + session + "]");
      session.close();}
  }

isSqlSessionTransactional

事务的断定逻辑:

  • 如果从事务管理器中获取, 阐明以后线程是有事务的
  • 以后线程中的事务 SqlSession 和这个办法中的 SqlSession 是同一个, 阐明是嵌套事务。

如果是 Spring 来治理事务, 这就不会主动来提交事务。外包给 Spring 的事务拦截器本人去解决。

  public static boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) {notNull(session, NO_SQL_SESSION_SPECIFIED);
    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);

    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    return (holder != null) && (holder.getSqlSession() == session);
  }

好了,到这里 Mybatis 中事务的解决逻辑咱们就到理解了。

SqlSession 对底层进行封装提供具体的指令
SqlSessionManager 和 SqlSessionTemplate 都是对 SqlSession 进行加强来主动或者委派 Spring 进行事务的解决的。

上面咱们去看看 Spring 是如何来处理事务的吧。Spring 事务的解决形式

感谢您的浏览,本文由 西魏陶渊明 版权所有。如若转载,请注明出处:西魏陶渊明(https://blog.springlearn.cn/)

本文由 mdnice 多平台公布

退出移动版