浅谈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 上的回调钩子实现的。

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

(0)

相关推荐

  • MyBatis在Spring环境下的事务管理

    MyBatis的设计思想很简单,可以看做是对JDBC的一次封装,并提供强大的动态SQL映射功能.但是由于它本身也有一些缓存.事务管理等功能,所以实际使用中还是会碰到一些问题--另外,最近接触了JFinal,其思想和Hibernate类似,但要更简洁,和MyBatis的设计思想不同,但有一点相同:都是想通过简洁的设计最大限度地简化开发和提升性能--说到性能,前段时间碰到两个问题: 1.在一个上层方法(DAO方法的上层)内删除一条记录,然后再插入一条相同主键的记录时,会报主键冲突的错误. 2.某些项

  • SpringMVC+MyBatis 事务管理(实例)

    前言 spring事务管理包含两种情况,编程式事务.声明式事务.而声明式事务又包括基于注解@Transactional和tx+aop的方式.那么本文先分析编程式注解事务和基于注解的声明式事务. 编程式事务管理使用TransactionTemplate或者PlatformTransactionManager.对于编程式事务spring推荐使用TransactionTemplate. 一.编程式事务 spring事务特性 spring中所有的事务策略类都继承自org.springframework.

  • Spring3.1.1+MyBatis3.1.1的增、删、查、改以及分页和事务管理

    1. [代码]Mybatis全局配置文件 <plugins> < plugin interceptor = "com.has.core.page.PaginationInterceptor" /> </plugins> 2. [文件] PaginationInterceptor.java @Intercepts ({ @Signature (type = StatementHandler. class , method = "prepare

  • SpringMVC+MyBatis声明式事务管理

    采用的基本搭建环境:SpringMVC.MyBatis.MySQL.tomcat Spring事务管理分解了传统的全局事务管理和本地事务管理的劣势,使得在任何环境中都可以使用统一的事务管理模型,你可以写一次代码,然后在不同的环境从你的代码里面配置不同的事务管理策略,Spring提供两种事务管理策略:一种是声明式事务管理策略,另一种是编程式事务管理策略,这里主要介绍声明式事务管理策略 由于采用的是SpringMVC. MyBatis,故统一采用了标注来声明Service.Controller 由于

  • 浅谈MyBatis 事务管理

    1. 运行环境 Enviroment 当 MyBatis 与不同的应用结合时,需要不同的事务管理机制.与 Spring 结合时,由 Spring 来管理事务:单独使用时需要自行管理事务,在容器里运行时可能由容器进行管理. MyBatis 用 Enviroment 来表示运行环境,其封装了三个属性: public class Configuration { // 一个 MyBatis 的配置只对应一个环境 protected Environment environment; // 其他属性 ...

  • 浅谈Mybatis SqlSession执行流程

    目录 Mybatis执行SQL流程 SqlSession Executor Mybatis之Executor Mybatis之StatementHandler 进入ResultSetHandler Mybatis执行SQL流程 在看源码之前,我们需要了解一些基本知识,如果您没有阅读Mybatis SqlSessionFactory 初始化原理,可以先阅读Mybatis SqlSessionFactory 初始化原理这篇文章,这用更有助于我们理解接下来的文章 在看源码之前,我们需要了解一些基本知识

  • 浅谈mybatis中的#和$的区别 以及防止sql注入的方法

    mybatis中的#和$的区别 1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号.如:order by #user_id#,如果传入的值是111,那么解析成sql时的值为order by "111", 如果传入的值是id,则解析成的sql为order by "id". 2. $将传入的数据直接显示生成在sql中.如:order by $user_id$,如果传入的值是111,那么解析成sql时的值为order by user_id,  如果传入的

  • 浅谈mybatis中的#和$的区别

    1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号.如:order by #user_id#,如果传入的值是111,那么解析成sql时的值为order by "111", 如果传入的值是id,则解析成的sql为order by "id". 2. $将传入的数据直接显示生成在sql中.如:order by $user_id$,如果传入的值是111,那么解析成sql时的值为order by user_id, 如果传入的值是id,则解析成的sql为ord

  • 浅谈java内存管理与内存溢出异常

    说到内存管理,笔者这里想先比较一下java与C.C++之间的区别: 在C.C++中,内存管理是由程序员负责的,也就是说程序员既要完成繁重的代码编写工作又要时常考虑到系统内存的维护 在java中,程序员无需考虑内存的控制和维护,而是交由JVM自动管理,这样就不容易出现内存泄漏和溢出的问题.然而,一旦出现内存泄漏和溢出方面的问题,如果不了解JVM的内存管理机制就很难找到错误所在. 1.JVM运行时数据区 JVM在运行java程序的时候会将它所管理的内存划分为若干个不同的区域,这些区域不仅有自己的用途

  • 浅谈vue权限管理实现及流程

    一.整体思路 后端返回用户权限,前端根据用户权限处理得到左侧菜单:所有路由在前端定义好,根据后端返回的用户权限筛选出需要挂载的路由,然后使用 addRoutes 动态挂载路由. 二.实现要点 (1)路由定义,分为初始路由和动态路由,一般来说初始路由只有 login,其他路由都挂载在 home 路由之下需要动态挂载. (2)用户登录,登录成功之后得到 token,保存在 sessionStorage,跳转到 home,此时会进入路由拦截根据 token 获取用户权限列表. (3)全局路由拦截,根据

  • 浅谈Mybatis版本升级踩坑及背后原理分析

    1.背景 某一天的晚上,系统服务正在进行常规需求的上线,因为发布时,提示统一的pom版本需要升级,于是从 1.3.9.6 升级至 1.4.2.1. 当服务开始上线后,开始陆续出现了一些更新系统交互日志方面的报警,属于系统辅助流程,报警下图所示, 具体系统数据已脱敏,内容是Mybatis相关的报警,在进行类型转换的时候,产生了强转错误. 更新开票请求返回日志, id:{#######}, response:{{"code":XXX,"data":{"call

  • 浅谈mybatis 乐观锁实现,解决并发问题

    情景展示: 银行两操作员同时操作同一账户就是典型的例子. 比如A.B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户扣除50元,A先提交,B后提交.最后实际账户余额为1000-50=950元,但本该为1000+100-50=1050.这就是典型的并发问题. 乐观锁机制在一定程度上解决了这个问题.乐观锁,大多是基于数据版本(Version)记录机制实现.何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 "

  • 浅谈Mybatis #和$区别以及原理

    总结: 1.#可以防止Sql 注入,它会将所有传入的参数作为一个字符串来处理. 2.$ 则将传入的参数拼接到Sql上去执行,一般用于表名和字段名参数,$ 所对应的参数应该由服务器端提供,前端可以用参数进行选择,避免 Sql 注入的风险 为什么? 为什么# 和 $ 的作用不同,Mybatis 对他们做了哪些惨无人道的处理,我们看一下下面的例子,并追踪一下源码总结. 示例代码: 创建一个 tb_class 表(具体字段不做解释). 创建一个 ClassDao.java 并使用注解的方式 ,table

随机推荐