mybatis sqlSession 工作原理
先看 SqlSession 的使用方式
// 加载全局配置⽂件
InputStream resourceAsStream = Resources.getResourceAsStream("myabtis-config.xml");
// 获得 sqlSession ⼯⼚对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 方式一
try (SqlSession session = sqlSessionFactory.openSession()) {
User user = (User) session.selectOne("com.stydu.mybatis.mapper.UserMapper.findById", 101);
}
// 方式二(面向切面的使用方式,后面讲)
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.findById(101);
}
可以看出 SqlSession 是从 SqlSessionFactory(DefaultSqlSessionFactory) 获取的,SqlSessionFactory 在初始化的时候就已经构建好了,包括一个 Configuration 对象, 前面已经分析过,这里就不再赘述了
SqlSession 是个非常重要的工具,是程序与数据库交互的桥梁,提供了一系列方法操作数据库,比如 selectOne、selectList、update、delete、insert 等;还提供了 getMapper 方法来获取 mapper 接口的代理对象,调用 mapper 接口的对象来简化数据库的操作;底层是通过其属性 Executor 调度各个组件配合工作达到目的
SqlSession 获取
DefaultSqlSessionFactory 获取 SqlSession 的关键代码
public class DefaultSqlSessionFactory implements SqlSessionFactory {
// 获取 SqlSession
public SqlSession openSession() {
// this.configuration.getDefaultExecutorType():默认是 org.apache.ibatis.session.ExecutorType#SIMPLE
// 第二个参数:事务隔离级别,null 表示使用数据库的隔离级别
// 第三个参数:true 表示事务自动提交,false 事务需要手动提交
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(),
(TransactionIsolationLevel)null,
false);
}
// 具体获取 SqlSession 关键代码
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
...
// 全局配置文件的 environment 节点
Environment environment = this.configuration.getEnvironment();
// environment 节点基本不会自定义配置事务工厂,所以这里会返回一个托管事务工厂
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
// 通过事务工厂生成一个事务(数据库连接、隔离级别、是否自动提交)
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 生成一个执行器(事务、执行器类型)
Executor executor = this.configuration.newExecutor(tx, execType);
// 生成一个 SqlSession,是 DefaultSqlSession(全局配置、执行器、是否自动提交)
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
...
return var8;
}
}
再看下 Executor 的创建
// org.apache.ibatis.session.Configuration#newExecutor
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
// 保证执行器类型不为空(防止因为粗心手动设置为 null 的情况吗?)
executorType = executorType == null ? this.defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
// 创建执行类型的执行器
Object executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
// 是否开启缓存(二级缓存)
if (this.cacheEnabled) {
executor = new CachingExecutor((Executor)executor);
}
// 插件
Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
return executor;
}
// org.apache.ibatis.plugin.InterceptorChain#pluginAll
public Object pluginAll(Object target) {
// 自己编写插件就要继承 Interceptor 类
for (Interceptor interceptor : interceptors) {
// 调用插件的 plugin 方法包装(自己编写插件要实现 plugin 方法)
target = interceptor.plugin(target);
}
return target;
}
SqlSession 工作流程
- executor 真正完成查询操作,有 4 个执行器:简单(SimpleExecutor)、重用(ReuseExecutor)、批量(BatchExecutor),缓存(CacheExecutor)
- 创建 Executor 时会明确是哪种,如果开启了二级缓存就使用 CacheExecutor,不然就是别的三种,默认是 SimpleExecutor
- 如果开启了缓存,使用 CacheExecutor 查询,如果没查到,还是会使用其余的三种去查询
看下 session.selectOne("com.stydu.mybatis.mapper.UserMapper.findById", 101);
底层怎么执行的
public <T> T selectOne(String statement) {
return this.selectOne(statement, (Object)null);
}
public <T> T selectOne(String statement, Object parameter) {
// 调用了 selectList
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
// 这个应该不陌生
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
try {
// 到 mappedStatements 里面获取 MappedStatement(初始化维护了 mappedStatements)
MappedStatement ms = this.configuration.getMappedStatement(statement);
// 可以看到底层的查询是 executor 完成的,看看里面做了什么
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception var9) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);
} finally {
ErrorContext.instance().reset();
}
return var5;
}
this.executor.query()
看这个方法做了什么
// org.apache.ibatis.executor.CachingExecutor#query
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
// org.apache.ibatis.executor.CachingExecutor#query
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 查询缓存(二级)
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 如果缓存未命中,调用 delegate 来查询,delegate 就是另外的三种执行器
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
可以明确的是,如果开启了二级缓存就是用 CachingExecutor 查询二级缓存,没有的话就调用别的查询器再次查询
如果不开启二级缓存直接调用别的查询器查询,少了一个查询二级缓存的步骤
假设就是默认的执行器,SimpleExecutor 中没有 query,所以调用父类 BaseExecutor 的 query 方法,源码如下:
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);
}
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
...
List<E> list;
try {
// 查询缓存(一级)
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--;
}
...
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 {
// 这个方法直接抛异常,太常见了,模板方法,调用子类实现的方法,所以又回到了 SimpleExecutor 中
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;
}
SimpleExecutor#doQuery 源码:
// org.apache.ibatis.executor.SimpleExecutor#doQuery
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
// 处理参数(根据 MappedStatement 具体类型创建 StatementHandler 对象的过程中处理工作完成)
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 获得数据库连接,创建 Statement 或者 PrepareStatement
stmt = this.prepareStatement(handler, ms.getStatementLog());
// 由 handler 执行 SQL 语句,并将数据库返回结果转化为设定的对象,比如 List,Map 或者是 POJO
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
SimpleStatementHandler#query 源码
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// 这里能看到 sql(带占位符的)
String sql = boundSql.getSql();
// 执行 sql
statement.execute(sql);
// 映射结果
return resultSetHandler.handleResultSets(statement);
}
总结
- 根据 SqlSessionFactoryBuilder 创建 SqlSessionFactory,其实是 DefaultSqlSessionFactory
- SqlSessionFactory.openSession 得到 SqlSession,其实是 DefaultSqlSession,内部持有一个 Executor 对象
- 调用方法时,SqlSession 先到 configuration.mappedStatements 中获取 MappedStatement
- Executor 根据 MappedStatement 查询数据
- 如果开启了二级缓存,先查询二级缓存
- 二级未命中或者根本没有使用二级,查询一级缓存
- 一级未命中才真正查询数据库
- 处理参数、创建连接、映射结果