浅谈MyBatis 事务管理

 更新时间:2019年10月27日 09:15:21   作者:coderbee笔记  
这篇文章主要介绍了浅谈MyBatis 事务管理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

1. 运行环境 Enviroment

当 MyBatis 与不同的应用结合时,需要不同的事务管理机制。与 Spring 结合时,由 Spring 来管理事务;单独使用时需要自行管理事务,在容器里运行时可能由容器进行管理。

MyBatis 用 Enviroment 来表示运行环境,其封装了三个属性:

public class Configuration {
  // 一个 MyBatis 的配置只对应一个环境
  protected Environment environment;
  // 其他属性 .....
}

public final class Environment {
  private final String id;
  private final TransactionFactory transactionFactory;
  private final DataSource dataSource;
}

2. 事务抽象

MyBatis 把事务管理抽象出 Transaction 接口,由 TransactionFactory 接口的实现类负责创建。

public interface Transaction {
  Connection getConnection() throws SQLException;
  void commit() throws SQLException;
  void rollback() throws SQLException;
  void close() throws SQLException;
  Integer getTimeout() throws SQLException;
}

public interface TransactionFactory {
  void setProperties(Properties props);
  Transaction newTransaction(Connection conn);
  Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}

Executor 的实现持有一个 SqlSession 实现,事务控制是委托给 SqlSession 的方法来实现的。

public abstract class BaseExecutor implements Executor {
  protected Transaction transaction;

  public void commit(boolean required) throws SQLException {
    if (closed) {
      throw new ExecutorException("Cannot commit, transaction is already closed");
    }
    clearLocalCache();
    flushStatements();
    if (required) {
      transaction.commit();
    }
  }

  public void rollback(boolean required) throws SQLException {
    if (!closed) {
      try {
        clearLocalCache();
        flushStatements(true);
      } finally {
        if (required) {
          transaction.rollback();
        }
      }
    }
  }

  // 省略其他方法、属性
}

3. 与 Spring 集成的事务管理

3.1 配置 TransactionFactory

与 Spring 集成时,通过 SqlSessionFactoryBean 来初始化 MyBatis 。

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
  Configuration configuration;

  XMLConfigBuilder xmlConfigBuilder = null;
  if (this.configuration != null) {
    configuration = this.configuration;
    if (configuration.getVariables() == null) {
      configuration.setVariables(this.configurationProperties);
    } else if (this.configurationProperties != null) {
      configuration.getVariables().putAll(this.configurationProperties);
    }
  } else if (this.configLocation != null) {
    xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
    configuration = xmlConfigBuilder.getConfiguration();
  } else {
    configuration = new Configuration();
    configuration.setVariables(this.configurationProperties);
  }

  if (this.objectFactory != null) {
    configuration.setObjectFactory(this.objectFactory);
  }

  if (this.objectWrapperFactory != null) {
    configuration.setObjectWrapperFactory(this.objectWrapperFactory);
  }

  if (this.vfs != null) {
    configuration.setVfsImpl(this.vfs);
  }

  if (hasLength(this.typeAliasesPackage)) {
    String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
    for (String packageToScan : typeAliasPackageArray) {
      configuration.getTypeAliasRegistry().registerAliases(packageToScan,
      typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
    }
  }

  if (!isEmpty(this.typeAliases)) {
    for (Class<?> typeAlias : this.typeAliases) {
      configuration.getTypeAliasRegistry().registerAlias(typeAlias);
    }
  }

  if (!isEmpty(this.plugins)) {
    for (Interceptor plugin : this.plugins) {
      configuration.addInterceptor(plugin);
    }
  }

  if (hasLength(this.typeHandlersPackage)) {
    String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
    for (String packageToScan : typeHandlersPackageArray) {
      configuration.getTypeHandlerRegistry().register(packageToScan);
    }
  }

  if (!isEmpty(this.typeHandlers)) {
    for (TypeHandler<?> typeHandler : this.typeHandlers) {
      configuration.getTypeHandlerRegistry().register(typeHandler);
    }
  }

  if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
    try {
      configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
    } catch (SQLException e) {
      throw new NestedIOException("Failed getting a databaseId", e);
    }
  }

  if (this.cache != null) {
    configuration.addCache(this.cache);
  }

  if (xmlConfigBuilder != null) {
    try {
      xmlConfigBuilder.parse();
    } catch (Exception ex) {
      throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  // 创建 SpringManagedTransactionFactory
  if (this.transactionFactory == null) {
    this.transactionFactory = new SpringManagedTransactionFactory();
  }

  // 封装成 Environment
  configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));

  if (!isEmpty(this.mapperLocations)) {
    for (Resource mapperLocation : this.mapperLocations) {
      if (mapperLocation == null) {
        continue;
      }

      try {
        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
        configuration, mapperLocation.toString(), configuration.getSqlFragments());
        xmlMapperBuilder.parse();
      } catch (Exception e) {
        throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  } else {
  }

  return this.sqlSessionFactoryBuilder.build(configuration);
}
public class SqlSessionFactoryBuilder {
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
}

重点是在构建 MyBatis Configuration 对象时,把 transactionFactory 配置成 SpringManagedTransactionFactory,再封装成 Environment 对象。

3.2 运行时事务管理

Mapper 的代理对象持有的是 SqlSessionTemplate,其实现了 SqlSession 接口。

SqlSessionTemplate 的方法并不直接调用具体的 SqlSession 的方法,而是委托给一个动态代理,通过代理 SqlSessionInterceptor 对方法调用进行拦截。

SqlSessionInterceptor 负责获取真实的与数据库关联的 SqlSession 实现,并在方法执行完后决定提交或回滚事务、关闭会话。

public class SqlSessionTemplate implements SqlSession, DisposableBean {
  private final SqlSessionFactory sqlSessionFactory;
  private final ExecutorType executorType;
  private final SqlSession sqlSessionProxy;
  private final PersistenceExceptionTranslator exceptionTranslator;

  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;

    // 因为 SqlSession 接口声明的方法也不少,
    // 在每个方法里添加事务相关的拦截比较麻烦,
    // 不如创建一个内部的代理对象进行统一处理。
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
      SqlSessionFactory.class.getClassLoader(),
      new Class[] { SqlSession.class },
      new SqlSessionInterceptor());
  }

  public int update(String statement) {
    // 在代理对象上执行方法调用
    return this.sqlSessionProxy.update(statement);
  }

  // 对方法调用进行拦截,加入事务控制逻辑
  private class SqlSessionInterceptor implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // 获取与数据库关联的会话
      SqlSession sqlSession = SqlSessionUtils.getSqlSession(
        SqlSessionTemplate.this.sqlSessionFactory,
        SqlSessionTemplate.this.executorType,
        SqlSessionTemplate.this.exceptionTranslator);
      try {
        // 执行 SQL 操作
        Object result = method.invoke(sqlSession, args);
        if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // 如果 sqlSession 不是 Spring 管理的,则要自行提交事务
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {

          SqlSessionUtils.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) {
          SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }
}

SqlSessionUtils 封装了对 Spring 事务管理机制的访问。

// SqlSessionUtils
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
  // 从 Spring 的事务管理机制那里获取当前事务关联的会话
  SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

  SqlSession session = sessionHolder(executorType, holder);
  if (session != null) {
    // 已经有一个会话则复用
    return session;
  }

  // 创建新的 会话
  session = sessionFactory.openSession(executorType);

  // 注册到 Spring 的事务管理机制里
  registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

  return session;
}

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) {
      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) {
      } else {
        throw new TransientDataAccessResourceException(
          "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
      }
    }
  } else {
  }
}

public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
  // 从线程本地变量里获取 Spring 管理的会话
  SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
  if ((holder != null) && (holder.getSqlSession() == session)) {
    // Spring 管理的不直接关闭,由回调钩子来关闭
    holder.released();
  } else {
    // 非 Spring 管理的直接关闭
    session.close();
  }
}

SqlSessionSynchronization 是 SqlSessionUtils 的内部私有类,用于作为回调钩子与 Spring 的事务管理机制协调工作,TransactionSynchronizationManager 在适当的时候回调其方法。

private static final class SqlSessionSynchronization extends TransactionSynchronizationAdapter {
  private final SqlSessionHolder holder;
  private final SqlSessionFactory sessionFactory;
  private boolean holderActive = true;

  public SqlSessionSynchronization(SqlSessionHolder holder, SqlSessionFactory sessionFactory) {
    this.holder = holder;
    this.sessionFactory = sessionFactory;
  }

  public int getOrder() {
    return DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 1;
  }

  public void suspend() {
    if (this.holderActive) {
      TransactionSynchronizationManager.unbindResource(this.sessionFactory);
    }
  }

  public void resume() {
    if (this.holderActive) {
      TransactionSynchronizationManager.bindResource(this.sessionFactory, this.holder);
    }
  }

  public void beforeCommit(boolean readOnly) {
    if (TransactionSynchronizationManager.isActualTransactionActive()) {
      try {
        this.holder.getSqlSession().commit();
      } catch (PersistenceException p) {
        if (this.holder.getPersistenceExceptionTranslator() != null) {
          DataAccessException translated = this.holder
            .getPersistenceExceptionTranslator()
            .translateExceptionIfPossible(p);
          if (translated != null) {
            throw translated;
          }
        }
        throw p;
      }
    }
  }

  public void beforeCompletion() {
    if (!this.holder.isOpen()) {
      TransactionSynchronizationManager.unbindResource(sessionFactory);
      this.holderActive = false;

      // 真正关闭数据库会话
      this.holder.getSqlSession().close();
    }
  }

  public void afterCompletion(int status) {
    if (this.holderActive) {
      TransactionSynchronizationManager.unbindResourceIfPossible(sessionFactory);
      this.holderActive = false;

      // 真正关闭数据库会话
      this.holder.getSqlSession().close();
    }
    this.holder.reset();
  }
}

3.3 创建新会话

// DefaultSqlSessionFactory
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);
    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();
  }
}

private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
  if (environment == null || environment.getTransactionFactory() == null) {
    return new ManagedTransactionFactory();
  }
  return environment.getTransactionFactory();
}

4. 小结

  1. MyBatis 的核心组件 Executor 通过 Transaction 接口来进行事务控制。
  2. 与 Spring 集成时,初始化 Configuration 时会把 transactionFactory 设置为 SpringManagedTransactionFactory 的实例。
  3. 每个 Mapper 代理里注入的 SqlSession 是 SqlSessionTemplate 的实例,其实现了 SqlSession 接口;
  4. SqlSessionTemplate 把对 SqlSession 接口里声明的方法调用委托给内部的一个动态代理,该代理的方法处理器为内部类 SqlSessionInterceptor 。
  5. SqlSessionInterceptor 接收到方法调用时,通过 SqlSessionUtil 访问 Spring 的事务设施,如果有与 Spring 当前事务关联的 SqlSession 则复用;没有则创建一个。
  6. SqlSessionInterceptor 根据 Spring 当前事务的状态来决定是否提交或回滚事务。会话的真正关闭是通过注册在 TransactionSynchronizationManager 上的回调钩子实现的。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • Spring MVC 关于controller的字符编码问题

    Spring MVC 关于controller的字符编码问题

    在使用springMVC框架构建web应用,客户端常会请求字符串、整型、json等格式的数据,通常使用@ResponseBody注解使 controller回应相应的数据而不是去渲染某个页面。
    2017-03-03
  • Java中Executor接口用法总结

    Java中Executor接口用法总结

    这篇文章主要介绍了Java中Executor接口用法,较为详细的总结了Executor接口的定义、创建及用法,需要的朋友可以参考下
    2015-06-06
  • Java返回分页结果集的封装代码实例

    Java返回分页结果集的封装代码实例

    这篇文章主要介绍了java返回分页结果集的封装代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-01-01
  • Spring Data JPA例子代码[基于Spring Boot、Mysql]

    Spring Data JPA例子代码[基于Spring Boot、Mysql]

    这篇文章主要介绍了Spring Data JPA例子代码[基于Spring Boot、Mysql],小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-04-04
  • 详解如何保护SpringBoot配置文件中的敏感信息

    详解如何保护SpringBoot配置文件中的敏感信息

    使用过SpringBoot配置文件的朋友都知道,资源文件中的内容通常情况下是明文显示,安全性就比较低一些,所以为了提高安全性,就需要对配置文件中的敏感信息进行保护,下面就为大家介绍一下实现方法吧
    2023-07-07
  • java使用swt显示图片示例分享

    java使用swt显示图片示例分享

    这篇文章主要介绍了java使用swt显示图片示例,修改后就可变为图片浏览器,需要的朋友可以参考下
    2014-02-02
  • JAVA设计模式之调停者模式详解

    JAVA设计模式之调停者模式详解

    这篇文章主要介绍了JAVA设计模式之调停者模式详解,调停者模式是对象的行为模式,调停者模式包装了一系列对象相互作用的方式,使得这些对象不必相互明显引用,从而使它们可以较松散地耦合,需要的朋友可以参考下
    2015-04-04
  • 学习Java之Java中的异常处理机制详解

    学习Java之Java中的异常处理机制详解

    在本文中,小编将带领大家来学习Java的异常处理机制,包括异常机制、异常类型、如何捕获异常、如何抛出异常以及如何创建自定义异常等核心内容,感兴趣的同学跟着小编一起来看看吧
    2023-08-08
  • 剖析SpringCloud Feign中所隐藏的坑

    剖析SpringCloud Feign中所隐藏的坑

    这篇文章主要为大家介绍了剖析SpringCloud Feign中所隐藏的坑示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • Java中的LinkedList集合详解

    Java中的LinkedList集合详解

    这篇文章主要介绍了Java中的LinkedList集合详解,  LinkedList 是一个双向链表结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环),在任意位置插入删除都很方便,但是不支持随机取值,每次都只能从一端开始遍历,直到找到查询的对象,然后返回,需要的朋友可以参考下
    2023-09-09

最新评论