我是一段不羁的公告!
记得给艿艿这 3 个项目加油,添加一个 STAR 噢。
https://github.com/YunaiV/SpringBoot-Labs
https://github.com/YunaiV/onemall
https://github.com/YunaiV/ruoyi-vue-pro

精尽 MyBatis 源码解析 —— Spring 集成(三)之 SqlSession

1. 概述

本文我们就来看看,Spring 和 MyBatis 的 SqlSession 是如何集成。

2. SqlSessionTemplate

org.mybatis.spring.SqlSessionTemplate ,实现 SqlSession、DisposableBean 接口,SqlSession 操作模板实现类。实际上,代码实现和 org.apache.ibatis.session.SqlSessionManager 超级相似。或者说,这是 mybatis-spring 项目实现的 “SqlSessionManager” 类。

2.1 构造方法

// SqlSessionTemplate.java

private final SqlSessionFactory sqlSessionFactory;

/**
* 执行器类型
*/
private final ExecutorType executorType;
/**
* SqlSession 代理对象
*/
private final SqlSession sqlSessionProxy;
/**
* 异常转换器
*/
private final PersistenceExceptionTranslator exceptionTranslator;

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
this(sqlSessionFactory, executorType,
new MyBatisExceptionTranslator(
sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}

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;
// <1> 创建 sqlSessionProxy 对象
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[]{SqlSession.class},
new SqlSessionInterceptor());
}

// ... 省略 setting 方法
  • executorType 属性,执行器类型。后续,根据它来创建对应类型的执行器。
  • sqlSessionProxy 属性,SqlSession 代理对象。在 <1> 处,进行创建 sqlSessionProxy 对象,使用的方法拦截器是 SqlSessionInterceptor 类。😈 是不是发现和 SqlSessionManager 灰常像。
  • exceptionTranslator 属性,异常转换器。

2.2 对 SqlSession 的实现方法

① 如下是直接调用 sqlSessionProxy 对应的方法。代码如下:

// SqlSessionTemplate.java

@Override
public <T> T selectOne(String statement) {
return this.sqlSessionProxy.selectOne(statement);
}

@Override
public <T> T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.selectOne(statement, parameter);
}

@Override
public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
return this.sqlSessionProxy.selectMap(statement, mapKey);
}

@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
return this.sqlSessionProxy.selectMap(statement, parameter, mapKey);
}

@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
return this.sqlSessionProxy.selectMap(statement, parameter, mapKey, rowBounds);
}

@Override
public <T> Cursor<T> selectCursor(String statement) {
return this.sqlSessionProxy.selectCursor(statement);
}

@Override
public <T> Cursor<T> selectCursor(String statement, Object parameter) {
return this.sqlSessionProxy.selectCursor(statement, parameter);
}

@Override
public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
return this.sqlSessionProxy.selectCursor(statement, parameter, rowBounds);
}

@Override
public <E> List<E> selectList(String statement) {
return this.sqlSessionProxy.selectList(statement);
}

@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.sqlSessionProxy.selectList(statement, parameter);
}

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
return this.sqlSessionProxy.selectList(statement, parameter, rowBounds);
}

@Override
public void select(String statement, ResultHandler handler) {
this.sqlSessionProxy.select(statement, handler);
}

@Override
public void select(String statement, Object parameter, ResultHandler handler) {
this.sqlSessionProxy.select(statement, parameter, handler);
}

@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);
}

@Override
public int insert(String statement) {
return this.sqlSessionProxy.insert(statement);
}

@Override
public int insert(String statement, Object parameter) {
return this.sqlSessionProxy.insert(statement, parameter);
}

@Override
public int update(String statement) {
return this.sqlSessionProxy.update(statement);
}

@Override
public int update(String statement, Object parameter) {
return this.sqlSessionProxy.update(statement, parameter);
}

@Override
public int delete(String statement) {
return this.sqlSessionProxy.delete(statement);
}

@Override
public int delete(String statement, Object parameter) {
return this.sqlSessionProxy.delete(statement, parameter);
}

@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}

@Override
public void clearCache() {
this.sqlSessionProxy.clearCache();
}

@Override
public Configuration getConfiguration() {
return this.sqlSessionFactory.getConfiguration();
}

@Override
public Connection getConnection() {
return this.sqlSessionProxy.getConnection();
}

@Override
public List<BatchResult> flushStatements() {
return this.sqlSessionProxy.flushStatements();
}

② 如下是不支持的方法,直接抛出 UnsupportedOperationException 异常。代码如下:

// SqlSessionTemplate.java

@Override
public void commit() {
throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
}

@Override
public void commit(boolean force) {
throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
}

@Override
public void rollback() {
throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
}

@Override
public void rollback(boolean force) {
throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
}

@Override
public void close() {
throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession");
}
  • 和事务相关的方法,不允许手动调用。

2.3 destroy

// SqlSessionTemplate.java

@Override
public void destroy() throws Exception {
//This method forces spring disposer to avoid call of SqlSessionTemplate.close() which gives UnsupportedOperationException
}

2.4 SqlSessionInterceptor

SqlSessionInterceptor ,是 SqlSessionTemplate 的内部类,实现 InvocationHandler 接口,将 SqlSession 的操作,路由到 Spring 托管的事务管理器中。代码如下:

// SqlSessionTemplate.java

/**
* Proxy needed to route MyBatis method calls to the proper SqlSession got
* from Spring's Transaction Manager
* It also unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to
* pass a {@code PersistenceException} to the {@code PersistenceExceptionTranslator}.
*/
private class SqlSessionInterceptor implements InvocationHandler {

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// <1> 获得 SqlSession 对象
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
// 执行 SQL 操作
Object result = method.invoke(sqlSession, args);
// 如果非 Spring 托管的 SqlSession 对象,则提交事务
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) {
// <4.1> 如果是 PersistenceException 异常,则进行转换
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
// <4.2> 根据情况,关闭 SqlSession 对象
// 如果非 Spring 托管的 SqlSession 对象,则关闭 SqlSession 对象
// 如果是 Spring 托管的 SqlSession 对象,则减少其 SqlSessionHolder 的计数
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
// <4.3> 置空,避免下面 final 又做处理
sqlSession = null;
// <4.4> 进行转换
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
// <4.5> 抛出异常
throw unwrapped;
} finally {
// <5> 根据情况,关闭 SqlSession 对象
// 如果非 Spring 托管的 SqlSession 对象,则关闭 SqlSession 对象
// 如果是 Spring 托管的 SqlSession 对象,则减少其 SqlSessionHolder 的计数
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
  • 类上的英文注释,胖友可以耐心看下。
  • <1> 处,调用 SqlSessionUtils#getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) 方法,获得 SqlSession 对象。此处,和 Spring 事务托管的事务已经相关。详细的解析,见 《精尽 MyBatis 源码解析 —— Spring 集成(四)之事务》 中。下面,和事务相关的,我们也统一放在该文中。
  • <2> 处,调用 Method#invoke(sqlSession, args) 方法,反射执行 SQL 操作
  • <3> 处,调用 SqlSessionUtils#isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) 方法,判断是否为 Spring 托管的 SqlSession 对象,则调用 SqlSession#commit(true) 方法,提交事务。
  • <4.1> 处,如果是 PersistenceException 异常,则:
    • <4.2> 处,调用 SqlSessionUtils#closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) 方法,根据情况,关闭 SqlSession 对象。1)如果非 Spring 托管的 SqlSession 对象,则关闭 SqlSession 对象。2)如果是 Spring 托管的 SqlSession 对象,则减少其 SqlSessionHolder 的计数。😈 也就是说,Spring 托管事务的情况下,最终是在“外部”执行最终的事务处理。
    • <4.3> 处,置空 sqlSession 属性,避免下面 final 又做关闭处理。
    • <4.4> 处,调用 MyBatisExceptionTranslator#translateExceptionIfPossible(RuntimeException e) 方法,转换异常。
    • <4.5> 处,抛出异常。
  • <5> 处,如果 sqlSession 非空,则调用 SqlSessionUtils#closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) 方法,根据情况,关闭 SqlSession 对象。

3. SqlSessionDaoSupport

org.mybatis.spring.support.SqlSessionDaoSupport ,继承 DaoSupport 抽象类,SqlSession 的 DaoSupport 抽象类。代码如下:

// SqlSessionDaoSupport.java

public abstract class SqlSessionDaoSupport extends DaoSupport {

/**
* SqlSessionTemplate 对象
*/
private SqlSessionTemplate sqlSessionTemplate;

/**
* Set MyBatis SqlSessionFactory to be used by this DAO.
* Will automatically create SqlSessionTemplate for the given SqlSessionFactory.
*
* @param sqlSessionFactory a factory of SqlSession
*/
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory); // 使用 sqlSessionFactory 属性,创建 sqlSessionTemplate 对象
}
}

/**
* Create a SqlSessionTemplate for the given SqlSessionFactory.
* Only invoked if populating the DAO with a SqlSessionFactory reference!
* <p>Can be overridden in subclasses to provide a SqlSessionTemplate instance
* with different configuration, or a custom SqlSessionTemplate subclass.
* @param sqlSessionFactory the MyBatis SqlSessionFactory to create a SqlSessionTemplate for
* @return the new SqlSessionTemplate instance
* @see #setSqlSessionFactory
*/
@SuppressWarnings("WeakerAccess")
protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}

/**
* Return the MyBatis SqlSessionFactory used by this DAO.
*
* @return a factory of SqlSession
*/
public final SqlSessionFactory getSqlSessionFactory() {
return (this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null);
}


/**
* Set the SqlSessionTemplate for this DAO explicitly,
* as an alternative to specifying a SqlSessionFactory.
*
* @param sqlSessionTemplate a template of SqlSession
* @see #setSqlSessionFactory
*/
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSessionTemplate = sqlSessionTemplate;
}

/**
* Users should use this method to get a SqlSession to call its statement methods
* This is SqlSession is managed by spring. Users should not commit/rollback/close it
* because it will be automatically done.
*
* @return Spring managed thread safe SqlSession
*/
public SqlSession getSqlSession() {
return this.sqlSessionTemplate;
}

/**
* Return the SqlSessionTemplate for this DAO,
* pre-initialized with the SessionFactory or set explicitly.
* <p><b>Note: The returned SqlSessionTemplate is a shared instance.</b>
* You may introspect its configuration, but not modify the configuration
* (other than from within an {@link #initDao} implementation).
* Consider creating a custom SqlSessionTemplate instance via
* {@code new SqlSessionTemplate(getSqlSessionFactory())}, in which case
* you're allowed to customize the settings on the resulting instance.
*
* @return a template of SqlSession
*/
public SqlSessionTemplate getSqlSessionTemplate() {
return this.sqlSessionTemplate;
}

/**
* {@inheritDoc}
*/
@Override
protected void checkDaoConfig() {
notNull(this.sqlSessionTemplate, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}

}
  • 主要用途是,sqlSessionTemplate 属性的初始化,或者注入。
  • 具体示例,可以看看 org.mybatis.spring.sample.dao.UserDaoImpl 类。

4. 异常相关

mybatis-spring 项目中,有两个异常相关的类:

  • org.mybatis.spring.MyBatisExceptionTranslator ,MyBatis 自定义的异常转换器。
  • org.mybatis.spring.MyBatisSystemException ,MyBatis 自定义的异常类。

代码比较简单,胖友自己去看。

666. 彩蛋

简单水更,哈哈哈哈。

参考和推荐如下文章:

总访客数 && 总访问量