code-learning/mybatis/31-mybatis-Spring 集成(三)之 SqlSession.md

445 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 精尽 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 集成(四)之事务》](http://svip.iocoder.cn/MyBatis/Spring-Integration-4) 中。下面,和事务相关的,我们也统一放在该文中。
- `<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. 彩蛋
简单水更,哈哈哈哈。
参考和推荐如下文章:
- 大新博客 [《Mybatis SqlSessionTemplate 源码解析》](https://www.cnblogs.com/daxin/p/3544188.html)