16 KiB
精尽 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. 彩蛋
简单水更,哈哈哈哈。
参考和推荐如下文章: