读懂MyBatis中的SqlSessionFactoryBuilder、SqlSessionFactory 和SqlSession
概述:
SqlSessionFactoryBuilder
、SqlSessionFactory
和 SqlSession
是 MyBatis 框架中负责数据库操作和会话管理的核心组件。
-
SqlSessionFactoryBuilder
是一个建造者模式的实现,它负责解析配置文件和构建SqlSessionFactory
实例。这个过程通常在应用程序启动时执行一次,因为SqlSessionFactory
的创建是资源密集型的,并且它的设计是作为一个单例存在。 -
SqlSessionFactory
是一个工厂模式的实现,提供了一个创建SqlSession
实例的方法。SqlSessionFactory
作为核心配置的持有者,保证了 MyBatis 的配置信息在应用程序运行期间只被加载和解析一次。 -
SqlSession
是 MyBatis 与数据库交互的主要接口,封装了执行 SQL 命令、管理事务和获取映射器(Mapper)的操作。它的生命周期应该是一个请求或事务的范围,确保每次数据库操作都是在一个干净的环境中进行。
在整个 MyBatis 的使用流程中,SqlSessionFactoryBuilder
构建 SqlSessionFactory
,SqlSessionFactory
生产 SqlSession
,而 SqlSession
则是执行具体的 SQL 操作和事务管理的地方。这三者的设计和协作体现了 MyBatis 对数据库访问层的简化和灵活性,同时也允许开发者保持对 SQL 的完全控制。
MyBatis 的设计理念之一是提供一个简单的编程 API,同时允许开发者保持对 SQL 语句的完全控制。 为了实现这一点,MyBatis 使用了工厂模式、建造者模式和模板方法模式等设计模式。
代码讲解:
下面是 SqlSessionFactoryBuilder
、SqlSessionFactory
和 SqlSession
的实现原理和设计理念的细化讲解。
SqlSessionFactoryBuilder
SqlSessionFactoryBuilder
使用了建造者模式,它的主要职责是基于配置信息构建并返回一个 SqlSessionFactory
实例。建造者模式使得对象的构建过程和表示方式分离,提供了更大的构建灵活性。
在 MyBatis 中,SqlSessionFactoryBuilder
通常使用 XML 配置文件或者注解方式来构建 SqlSessionFactory
。它会解析配置文件,创建 Configuration
对象,该对象包含了所有的配置信息,如数据源、事务管理器、映射文件位置等。这个过程涉及到解析 XML、注册映射器、解析注解等一系列操作。
SqlSessionFactory
SqlSessionFactory
是一个接口,它定义了创建 SqlSession
的方法。SqlSessionFactory
的实现类(如 DefaultSqlSessionFactory
)持有配置信息 Configuration
对象,并负责创建 SqlSession
实例。
SqlSessionFactory
的设计理念是单例模式,即在应用程序的生命周期中只存在一个 SqlSessionFactory
实例。这是因为 SqlSessionFactory
的创建过程通常是资源密集型的,且其内部包含了所有的配置信息,因此没必要重复创建。
SqlSession
SqlSession
是 MyBatis 提供的一个核心接口,它封装了与数据库交互的所有方法,包括执行 SQL 语句、提交或回滚事务和获取映射器(Mapper)实例。SqlSession
的实现类(如 DefaultSqlSession
)通过内部的 Executor
对象来执行 SQL 语句。
SqlSession
的设计理念是遵循模板方法模式,它提供了一个执行数据库操作的模板,而具体的执行逻辑由 Executor
实现。这种设计允许 MyBatis 轻松地支持不同的执行策略,如批处理执行、简单执行和缓存执行。
实现原理
- 配置解析:当使用
SqlSessionFactoryBuilder
构建SqlSessionFactory
时,MyBatis 首先解析配置文件,创建Configuration
对象,并初始化所有配置信息。 - SqlSessionFactory 创建:
SqlSessionFactoryBuilder
通过配置信息构建DefaultSqlSessionFactory
实例,该实例将被用来创建SqlSession
。 - SqlSession 创建:调用
SqlSessionFactory.openSession()
方法,会创建一个DefaultSqlSession
实例。这个实例包含了执行 SQL 操作所需的所有方法,并且通过Executor
实现实际的数据库交互。
设计理念
- 分离关注点:通过将配置解析、会话工厂创建和数据库会话管理分离,MyBatis 使得每个组件都专注于自己的职责,从而提高了代码的可维护性和可扩展性。
- 灵活性和控制权:MyBatis 允许开发者通过 XML 或注解自定义 SQL 语句,这提供了对 SQL 的细粒度控制,并且可以灵活地应对复杂的数据库操作。
- 易用性和简洁性:MyBatis 的 API 设计简洁,易于理解和使用,同时隐藏了底层的复杂性,如事务管理和资源释放。
MyBatis 的设计理念和实现原理相结合,提供了一个既强大又灵活的持久层框架,它允许开发者以最接近 SQL 的方式进行数据库操作,同时提供了 ORM 框架的便利
要深入理解 SqlSessionFactoryBuilder
、SqlSessionFactory
和 SqlSession
的实现原理,需要查看 MyBatis 源码。
以下是基于 MyBatis 3.4.5 版本的源码解读。请注意,由于源码较长,这里只提供关键部分的伪代码和解释。
SqlSessionFactoryBuilder
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(Reader reader) {
// 使用 XMLConfigBuilder 解析配置文件
XMLConfigBuilder parser = new XMLConfigBuilder(reader);
return build(parser.parse());
}
public SqlSessionFactory build(Configuration config) {
// 创建 DefaultSqlSessionFactory 实例
return new DefaultSqlSessionFactory(config);
}
}
在上述代码中,
SqlSessionFactoryBuilder
提供了多个 build()
方法的重载,用于从不同的配置源创建 SqlSessionFactory
。XMLConfigBuilder
负责解析 XML 配置文件,并将解析结果填充到 Configuration
对象中。
最终,SqlSessionFactoryBuilder
使用这个 Configuration
对象来创建 DefaultSqlSessionFactory
实例。
SqlSessionFactory
public interface SqlSessionFactory {
SqlSession openSession();
// ... 其他方法
}
SqlSessionFactory 接口类,定义各类实现openSession 方法
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
public SqlSession openSession() {
// 创建 Executor,Executor 是 MyBatis 的核心调度器
Executor executor = configuration.newExecutor();
// 创建 DefaultSqlSession 实例
return new DefaultSqlSession(configuration, executor);
}
}
SqlSessionFactory
接口定义了 openSession()
方法,用于创建 SqlSession
。DefaultSqlSessionFactory
是它的实现类,持有 Configuration
对象。在 openSession()
方法中,它首先创建一个 Executor
实例,然后使用这个 Executor
和 Configuration
创建 DefaultSqlSession
。
3.4.5版本中的mybatis openSession调用openSessionFromDataSource或openSessionFromConnection进行创建DefaultSqlSession
SqlSession
public interface SqlSession extends Closeable {
<T> T selectOne(String statement, Object parameter);
// ... 其他方法
}
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
public DefaultSqlSession(Configuration configuration, Executor executor) {
this.configuration = configuration;
this.executor = executor;
}
public <T> T selectOne(String statement, Object parameter) {
// 调用 Executor 执行查询
return executor.query(...);
}
// ... 实现其他方法
}
SqlSession
接口定义了数据库操作所需的方法,如 selectOne()
。DefaultSqlSession
是它的实现类,它组合了 Configuration
和 Executor
。在 selectOne()
方法中,它实际上委托给 Executor
来执行查询。
Executor
public interface Executor {
<E> List<E> query(MappedStatement ms, Object parameter, ResultHandler resultHandler);
// ... 其他方法
}
public class BaseExecutor implements Executor {
@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;
}
// ... 实现其他方法
}
Executor
接口定义了 SQL 执行的方法,BaseExecutor
是它的一个实现。在 query()
方法中,它负责处理查询逻辑,包括可能的缓存处理、参数处理和 SQL 语句构建。
重点讲解一下这个段代码:
这段代码是 MyBatis 中 Executor
接口的一个实现类BaseExecutor,它是执行查询操作的核心逻辑。下面是对这段代码的逐行解读:
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
这是 query
方法的签名,它被覆盖(Override)以提供具体的实现。这个方法返回一个 List<E>
,即查询结果的列表。MappedStatement
包含了 SQL 语句及其相关配置信息。parameter
是 SQL 语句的参数。RowBounds
用于分页。ResultHandler
用于处理查询结果。CacheKey
是缓存键。BoundSql
包含了最终要执行的 SQL 语句和参数。
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
这行代码设置了错误上下文(ErrorContext),以便在发生异常时提供有关资源、正在执行的活动和对象(通常是 SQL 语句的 ID)的详细信息。
if (closed) {
throw new ExecutorException("Executor was closed.");
}
检查 Executor
是否已关闭,如果已关闭,则抛出 ExecutorException
异常。
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
在查询开始之前,如果这是最外层的查询(queryStack
为 0),并且 MappedStatement
指示需要清空缓存,则清空本地缓存。
List<E> list;
try {
queryStack++;
声明一个用于存储结果的列表,并且在尝试执行查询之前,增加 queryStack
的值,表示当前有一个活跃的查询。
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
如果没有提供 ResultHandler
,则尝试从本地缓存中获取结果。如果结果存在,则不需要访问数据库。
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
如果从缓存中获取到了结果,则处理缓存的输出参数。如果缓存中没有结果,则调用 queryFromDatabase
方法从数据库查询数据
} finally {
queryStack--;
}
无论查询是否成功,最终都会减少 queryStack
的值,表示当前查询已完成。
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
如果所有的查询都已执行完毕(queryStack
为 0),则执行所有延迟加载的结果,并清空延迟加载列表。
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
如果配置了在语句级别清除本地缓存,则在查询结束后清除缓存。
return list;
}
返回查询结果列表。
这段代码展示了 MyBatis 如何处理查询请求,包括缓存的使用、延迟加载的处理以及错误上下文的管理。这是一个典型的模板方法模式的应用,其中 queryFromDatabase
方法可能被子类覆盖以提供不同的查询行为。
总结
SqlSessionFactoryBuilder
、SqlSessionFactory
和 SqlSession
的设计和实现体现了 MyBatis 的核心原则:保持简单和灵活性。通过分离配置解析、会话工厂创建和数据库会话管理,MyBatis 提供了易于使用的 API,同时允许开发者对 SQL 保持完全控制。Executor
作为 SQL 执行的核心组件,隐藏了底层的复杂性,如事务控制和缓存管理,使得开发者可以专注于业务逻辑。
在实际应用中,开发者通常与 SqlSessionFactory
和 SqlSession
直接交互,而 SqlSessionFactoryBuilder
主要在应用启动时使用一次。这种设计使得 MyBatis 在易用性和性能之间取得了平衡,为 Java 应用提供了一个高效的数据访问层。
转载自:https://juejin.cn/post/7363121890792161314