mybatis之通过SqlSessionTemplate解决每次都要openSession问题

写在前面

上篇文章中我们分析了在spring中mybatis的初始化过程,本片文章分析spring对于mybatis的org.apache.ibatis.session.SqlSession的封装,在直接使用mybatis时,因为SqlSession本身是非线程安装的,因此每次都需要通过openSession方法打开一个新的会话,这个过程模拟代码可能如下:

@Test
publicvoid multiInsertByQueryParamter() {
    // 假设用户A接口调用导致的数据库执行
    TestResultmapAndParametermapQueryDto newDto = new TestResultmapAndParametermapQueryDto();
    newDto.setRealAge(new Random().nextInt(100));
    newDto.setRealName(UUID.randomUUID().toString());
    this.sqlSessionFactory.openSession().getMapper(TestResultmapAndParametermapMapper.class).insertByQueryParamter(newDto);
    sqlSession.commit();

    // 假设用户B接口调用导致的数据库操作
    TestResultmapAndParametermapQueryDto newDto1 = new TestResultmapAndParametermapQueryDto();
    newDto1.setRealAge(new Random().nextInt(100));
    newDto1.setRealName(UUID.randomUUID().toString());
    this.sqlSessionFactory.openSession().getMapper(TestResultmapAndParametermapMapper.class).insertByQueryParamter(newDto1);
    sqlSession.commit();
}

注意到this.sqlSessionFactory.openSession()被重复调用了,而在spring中就不需要了,可能有如下的代码:

@Test
public void multiInsertOne() {
    TestResultmapAndParametermapMapper testResultmapAndParametermapMapper = ac.getBean(TestResultmapAndParametermapMapper.class);
    System.out.println(testResultmapAndParametermapMapper);
    TestResultmapAndParametermapQueryDto newDto = new TestResultmapAndParametermapQueryDto();
    newDto.setRealAge(new Random().nextInt(100));
    newDto.setRealName(UUID.randomUUID().toString());
    testResultmapAndParametermapMapper.insertByQueryParamter(newDto);

    TestResultmapAndParametermapQueryDto newDto1 = new TestResultmapAndParametermapQueryDto();
    newDto.setRealAge(new Random().nextInt(100));
    newDto.setRealName(UUID.randomUUID().toString());
    testResultmapAndParametermapMapper.insertByQueryParamter(newDto1);
}

注意到我们使用从spring的IOC容器中获取的testResultmapAndParametermapMapper,直接使用其进行了数据库操作,而并没有显式openSession操作,可以通过在org.apache.ibatis.session.defaults.DefaultSqlSession#update(java.lang.String, java.lang.Object)方法中打断点,来验证每次都是不同的SqlSession对象,如下是我本地的测试结果:

  • 第一次
    在这里插入图片描述
  • 第二次
    在这里插入图片描述
    spring实现这种功能依赖是其org.mybatis.spring.SqlSessionTemplate来实现的,下面我们就一起来看下这个功能具体是如何实现的。

1:SqlSessionTemplate

1.1:构造函数

org.mybatis.spring.SqlSessionTemplate#SqlSessionTemplate(org.apache.ibatis.session.SqlSessionFactory, org.apache.ibatis.session.ExecutorType, org.springframework.dao.support.PersistenceExceptionTranslator)
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {
  // 强制要求要mybatis的org.apache.mybatis.session.SqlSessionFactory
  notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
  // 强制要求有executorType,默认为org.apache.ibatis.session.ExecutorType#SIMPLE
  notNull(executorType, "Property 'executorType' is required");
  // 设置sqlSessionFactory
  this.sqlSessionFactory = sqlSessionFactory;
  // 设置executorType
  this.executorType = executorType;
  // 设置转换mybatis抛出的org.apache.ibatis.execption.PersistenceExecption的异常转换对象
  this.exceptionTranslator = exceptionTranslator;
  // 创建SqlSession的代理类
  // 注意这里调用的是java.lang.refelct.Proxy的newProxyInstance方法创建动态代理对象
  // 使用了静态导入:import static java.lang.reflect.Proxy.newProxyInstance;
  // 2021-09-03 15:29:04
  this.sqlSessionProxy = (SqlSession) newProxyInstance(
      SqlSessionFactory.class.getClassLoader(),
      new Class[] { SqlSession.class },
      new SqlSessionInterceptor());
}

2021-09-03 15:29:04处是生成SqlSession的代理类,这里我们需要关注的是SqlSessionInterceptor,具体参考1.2:SqlSessionInterceptor

1.2:SqlSessionInterceptor

源码如下:

org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor
// 
private class SqlSessionInterceptor implements InvocationHandler {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 获取被spring事务管理器托管的SqlSession
    // 2021-09-03 16:48:56
    SqlSession sqlSession = getSqlSession(
        SqlSessionTemplate.this.sqlSessionFactory,
        SqlSessionTemplate.this.executorType,
        SqlSessionTemplate.this.exceptionTranslator);
    try {
      // 反射调用org.apache.ibatis.session.SqlSession的目标方法获取结果
      Object result = method.invoke(sqlSession, args);
       // 如果不是spring事务管理器托管的SqlSession,则直接调用sqlSession的commit方法提交事务
      if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
        sqlSession.commit(true);
      }
      // 返回查询结果
      return result;
    } catch (Throwable t) {
      Throwable unwrapped = unwrapThrowable(t);
      // 如果是指定了异常转换对象,并且当前异常是org.apache.ibatis.exception.PersistenceException
      if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
        // 尝试关闭SqlSession
        // 2021-09-03 17:03:36
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        sqlSession = null;
        // 获取转换的异常对象
        Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
        // 如果转换的异常对象不为null,则赋值到当前异常
        if (translated != null) {
          unwrapped = translated;
        }
      }
      // 抛出异常,可能是原始异常,也可能是转换后的异常
      throw unwrapped;
    } finally {
      if (sqlSession != null) {
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }
  }
}

2021-09-03 16:48:56处是获取被spring事务管理器托管的SqlSession对象,关于此具体参考这篇文章2021-09-03 17:03:36处是尝试关闭sqlSesion,具体参考1.3.1:关闭SqlSession

1.3.1:关闭SqlSession

源码如下:

org.mybatis.spring.SqlSessionUtils#closeSqlSession
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
  // 非空判断
  notNull(session, NO_SQL_SESSION_SPECIFIED);
  notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
  // 获取封装SqlSession对象的SqlSessionHolder
  SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
  // 如果holder不为空,并且当前的sqlSession和和holder中获取的是相同的对象
  // 则说明当前的SqlSession是被spring的事务管理器托管的
  if ((holder != null) && (holder.getSqlSession() == session)) {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Releasing transactional SqlSession [" + session + "]");
    }
    // 直接调用release方法,后续由spring负责关闭会话
    holder.released();
  } else {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Closing non transactional SqlSession [" + session + "]");
    }
    // 如果不是spring事务管理器托管的SqlSession,则直接close
    session.close();
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值