Mybatis SqlSession 工作原理
1. 使用示例
// 加载全局配置⽂件
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);
}
可以看出 SqlSession 是从 SqlSessionFactory(DefaultSqlSessionFactory) 获取的,SqlSessionFactory 在初始化的时候就已经构建好了,其中已经维护好了一个 Configuration 对象
SqlSession 是个非常重要的工具,是程序与数据库交互的桥梁,提供了一系列方法操作数据库,比如 selectOne、selectList、update、delete、insert 等
还提供了 getMapper 方法来获取 mapper 接口的代理对象,调用 mapper 接口的对象来简化数据库的操作
2. 获取 SqlSession
public class DefaultSqlSessionFactory implements SqlSessionFactory {
// 获取 SqlSession
public SqlSession openSession() {
// 第一个参数:执行其类型,默认是 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;
try {
// 根据环境信息生成一个事务(数据库连接,隔离级别,是否自动提交)
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 生成一个 executor
Executor executor = this.configuration.newExecutor(tx, execType);
// 生成一个 sqlSession
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception e) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
}
2.1 生成 Executor 执行器
// org.apache.ibatis.session.Configuration#newExecutor
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
// 保证执行器类型不为空
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) {
// mybatis 的插件都会继承 Interceptor 接口,遍历所有插件层层套娃(mvc 的拦截器类似)
for (Interceptor interceptor : interceptors) {
// 调用插件的 plugin 方法包装(自己编写插件要实现 plugin 方法)
target = interceptor.plugin(target);
}
return target;
}
2.2 生成 DefaultSqlSession
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
Executor 是真正去执行 sql 的,有 4 种类型:简单(SimpleExecutor)、重用(ReuseExecutor)、批量(BatchExecutor),缓存(CacheExecutor)
创建 SqlSession 时会明确执行器是哪种,默认是 SimpleExecutor,如果开启了二级缓存是 CacheExecutor
3. SqlSession 工作原理
SqlSession.selectOne
是 MyBatis 中用于执行查询并返回单个结果的核心方法。它的实现流程涉及多个组件的协作,包括 Executor
、StatementHandler
、ResultSetHandler
等
3.1 SqlSession.selectOne
的入口
SqlSession
是 MyBatis 的核心接口,DefaultSqlSession
是其默认实现。selectOne
方法的定义如下:
public interface SqlSession extends Closeable {
<T> T selectOne(String statement, Object parameter);
}
在 DefaultSqlSession
中,selectOne
的实现如下:
@Override
public <T> T selectOne(String statement, Object parameter) {
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;
}
}
selectOne
方法实际上是调用selectList
方法,然后检查返回的列表大小。- 如果列表大小为 1,返回第一个元素。
- 如果列表大小大于 1,抛出
TooManyResultsException
异常。 - 如果列表为空,返回
null
。
3.2 selectList
的实现
selectList
是 selectOne
的核心实现。在 DefaultSqlSession
中,selectList
的实现如下:
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 获取 MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
// 通过 Executor 执行查询
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
MappedStatement
:封装了 SQL 语句的配置信息(如 SQL 语句、参数类型、结果类型等)。id
可以在不同的 Mapper 文件中重复,但必须配合命名空间来确保全限定名唯一。- 每个
MappedStatement
的唯一标识是 命名空间 + id,因此可以在不同的 Mapper 中使用相同的id
。
Executor
:执行器,负责执行 SQL 语句。wrapCollection
:将参数包装为 MyBatis 内部使用的格式。
3.3 Executor.query
的实现
Executor
是 MyBatis 的执行器接口,BaseExecutor
是其基础实现。query
方法的实现如下:
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取 BoundSql
BoundSql boundSql = ms.getBoundSql(parameter);
// 创建 CacheKey
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 执行查询
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// 检查缓存
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--;
}
return list;
}
BoundSql
:封装了最终的 SQL 语句和参数。CacheKey
:用于缓存的键。localCache
:本地缓存,用于存储查询结果。
3.4 queryFromDatabase
的实现
queryFromDatabase
是实际执行数据库查询的方法:
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
try {
// 在缓存中占位
localCache.putObject(key, EXECUTION_PLACEHOLDER);
// 执行查询
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 移除占位符
localCache.removeObject(key);
}
// 将结果放入缓存
localCache.putObject(key, list);
return list;
}
doQuery
:实际执行查询的方法。
3.5 doQuery
的实现
doQuery
是 SimpleExecutor
(BaseExecutor
的子类)中的方法:
@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
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 准备 Statement
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行查询
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
StatementHandler
:负责创建Statement
并执行 SQL 查询。prepareStatement
:准备Statement
,包括设置参数等。
3.6 StatementHandler.query
的实现
StatementHandler
是 MyBatis 的语句处理器接口,PreparedStatementHandler
是其实现类之一。query
方法的实现如下:
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行查询
ps.execute();
// 处理结果集
return resultSetHandler.handleResultSets(ps);
}
ps.execute()
:执行 SQL 查询。resultSetHandler.handleResultSets
:处理结果集并映射为 Java 对象。
3.7 ResultSetHandler.handleResultSets
的实现
ResultSetHandler
是 MyBatis 的结果集处理器接口,DefaultResultSetHandler
是其默认实现。handleResultSets
方法的实现如下:
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
final List<Object> multipleResults = new ArrayList<>();
ResultSet rs = null;
try {
// 获取结果集
rs = stmt.getResultSet();
// 处理结果集
while (rs != null) {
multipleResults.add(handleResultSet(rs));
rs = getNextResultSet(stmt);
}
} finally {
closeResultSet(rs);
}
return collapseSingleResultList(multipleResults);
}
handleResultSet
:将ResultSet
映射为 Java 对象。collapseSingleResultList
:将多个结果集合并为一个列表。
总结
SqlSession.selectOne
的实现流程可以概括为以下步骤:
-
selectOne
调用selectList
:selectOne
方法调用selectList
,并检查返回的列表大小。
-
selectList
调用Executor.query
:- 获取
MappedStatement
,并通过Executor
执行查询。
- 获取
-
Executor.query
调用queryFromDatabase
:- 检查缓存,如果缓存中没有结果,则从数据库中查询。
-
queryFromDatabase
调用doQuery
:- 实际执行数据库查询。
-
doQuery
调用StatementHandler.query
:- 创建
Statement
并执行 SQL 查询。
- 创建
-
StatementHandler.query
调用ResultSetHandler.handleResultSets
:- 处理结果集并映射为 Java 对象。
通过以上流程,SqlSession.selectOne
实现了从数据库查询数据并返回单个结果的功能。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析