Mybatis源码解析(三)------SqlSession
- 序言
- SqlSession接口
- SqlSession的实现类
- DefaultSqlSession
- Select
- 获取Statement
- 查询
序言
Mybatis里面的核心就是SqlSession这个接口,前面我们已经研究了Mybatis的配置过程和Mapper的注册过程,在本篇文章中我们就来研究SqlSession。
/**
* The primary Java interface for working with MyBatis.
* Through this interface you can execute commands, get mappers and manage transactions.
*
* @author Clinton Begin
*/
SqlSession接口
先来看看sqlSession的接口:
接口方法 | 接口描述 |
---|---|
<T> T selectOne(String statement); | 查询单行数据,无参 |
<T> T selectOne(String statement, Object parameter); | 带参数查询单行数据 |
<E> List<E> selectList(String statement); | 无参数查询集合 |
<E> List<E> selectList(String statement, Object parameter); | 带参数查询集合 |
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds); | 带参数查询指定行内的数据 |
<K, V> Map<K, V> selectMap(String statement, String mapKey); | selectMap 是一种特殊情况,它旨在根据结果对象中的一个属性将结果列表转换为 Map。 例如。 为 selectMap(“selectAuthors”,“id”) 返回 Map[Integer,Author] |
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey); | 带参数,同上 |
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds); | 带指定行数,同上 |
<T> Cursor<T> selectCursor(String statement); | Cursor和list一样,不过Cursor用Iterator来懒加载数据 |
<T> Cursor<T> selectCursor(String statement, Object parameter); | 带参数,同上 |
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds); | 带指定行数,同上 |
void select(String statement, Object parameter, ResultHandler handler); | 用ResultHandler来处理查询结果,返回一条数据(方法注释上是一条,handler注释上是处理每条,我觉得返回结果也是处理多条) |
void select(String statement, ResultHandler handler); | 不带参,同上 |
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler); | 带行数限制,同上 |
int insert(String statement); | 执行插入操作 |
int insert(String statement, Object parameter); | 带参数插入 |
int update(String statement); | 更新操作 |
int update(String statement, Object parameter); | 带参数更新 |
int delete(String statement); | 删除操作 |
int delete(String statement, Object parameter); | 带参数删除 |
void commit(); | 刷新批处理语句并提交数据库连接。 请注意,如果没有调用更新/删除/插入,则不会提交数据库连接。 强制提交调用commit(boolean) |
void commit(boolean force); | 刷新批处理语句并提交数据库连接。 |
void rollback(); | 丢弃挂起的批处理语句并回滚数据库连接。 请注意,如果没有调用更新/删除/插入,数据库连接将不会回滚。 强制回滚调用rollback(boolean) |
void rollback(boolean force); | 丢弃挂起的批处理语句并回滚数据库连接。 请注意,如果没有调用更新/删除/插入,数据库连接将不会回滚。 |
List<BatchResult> flushStatements(); | 刷新批处理语句。 |
void close(); | 关闭session连接 |
void clearCache(); | 清除本地缓存 |
Configuration getConfiguration(); | 获取当前的配置 |
<T> T getMapper(Class<T> type); | 获取Mapper |
Connection getConnection(); | 获取连接 |
看起来SqlSession接口定义很简单也很全面了,我们需要的数据库操作不外乎就是CRUD了。接着往下看…… |
SqlSession的实现类
Mybatis对SqlSession的实现有三个:DefaultSqlSession、SqlSessionManager、SqlSessionTemplate。
接下来分别对三个实现类进行分析。
DefaultSqlSession
SqlSession的默认实现
/**
* The default implementation for {@link SqlSession}.
* Note that this class is not Thread-Safe.
*
* @author Clinton Begin
*/
分别从CRUD来看看这个实现类吧。
Select
select是业务系统中使用最多的操作,根据源码绘制下图,从图中就可以看出,查询工作最终是落到了private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler)
上。
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
MappedStatement:在原文中也没有注释,我们用原始的jdbc的时候也会有一步操作,获取Statement。至于Statement翻译成中文是什么,还真不好直译,网上说的 Statement是Java 执行数据库操作的一个重要接口,这里的MappedStatement也就是这个意思吧。
这里Select的过程就是从configuration里面那到对应的statement然后交个executor进行查询。
获取Statement
取得过程也很简单,就是去一个Map里面通过statment字符串去取就好了:
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
if (validateIncompleteStatements) {
buildAllStatements();
}
return mappedStatements.get(id);
}
可是,MappedStatement是如何放到Map里面的呢?利用Idea看了下,是在Mapper注册的时候放进去的。关于Mapper的注册可以看下Mybatis源码解析(二)------Mapper注册,不过我也没太仔细分析注册过程。
查询
那到Statement后,执行器开始执行查询操作,执行查询前看下跟一下这里的执行器是哪个,这个要从SqlSession session = sqlSessionFactory.openSession();
开始看,SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
从这里得知是DefaultSqlSessionFactory
, 然后这样再那样,得到执行器是SimpleExecutor
,而SimpleExecutor
继承的是BaseExecutor
,并且没有重写query方法。那么接下来就是看看BaseExecutor
的query方法了。
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
先从ms中取出BoundSql,然后生成一个缓存key。
BoundSql是什么?在处理完动态内容后,从SqlSource中取出的一个实际的Sql字符串,这条sql可能带有占位符?,还有一个参数映射集合,以及一些参数描述性信息。
换句话说就是BoundSQL就是解析我们的Mapper生成的,每个接口方法和sql对应一个BoundSql。
然后继续查询:
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
先从缓存中取,然后没取到就从数据库查。从数据库取的时候肯定得王缓存里面放。
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
doQuery是一个抽象方法,有子类实现。
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException;
看下SimpleExecutor的doQuery方法吧:
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
先看下StatementHandler的:
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
………………
}
上面这个结构,我觉着是啥设计模式呢?对设计模式了解不是非常深入,说是代理模式也不像啊,把整个对象都放给调用者了。有了解设计模式的大佬指教下呢。
在Demo这个场景下,这边用的是PreparedStatementHandler;接着,用StatementHandler来准备一个Statement,看下准备过程准备了什么:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}