MyBatis 作为一款经典的持久层框架,其设计精妙之处在于通过几个核心组件的协作,将 SQL 操作与 Java 对象优雅地结合起来。本文将深入剖析 MyBatis 的核心组件,包括它们的作用、相互关系以及底层实现原理。
1.MyBatis 核心组件概览
MyBatis 的核心组件主要包括以下几个部分:
- SqlSessionFactoryBuilder:负责从 XML 配置文件或 Java 代码中构建 SqlSessionFactory。
- SqlSessionFactory:工厂模式的实现,负责创建 SqlSession 实例。
- SqlSession:提供了执行 SQL 命令的方法,是应用与 MyBatis 之间的主要编程接口。
- Executor:SqlSession 内部使用 Executor 来执行 SQL 语句。
- Mapper 接口与映射文件:定义 SQL 语句与 Java 方法的映射关系。
- TypeHandler:负责 Java 类型与 JDBC 类型之间的转换。
- ParameterHandler:处理 SQL 参数。
- ResultSetHandler:处理 SQL 查询结果集。
这些组件相互协作,形成了 MyBatis 的核心架构。下面我们通过一个整体架构图来直观地了解它们之间的关系:
+-------------------+ +-------------------+ +-------------------+
| | | | | |
| SqlSessionFactory |<--->| SqlSession |<--->| MapperProxy |
| | | | | |
+-------------------+ +-------------------+ +-------------------+
^ | ^ ^
| | | |
| v | |
+-------------------+ +-------------------+ +-------------------+
| | | | | |
| Configuration | | Executor | | MapperRegistry |
| | | | | |
+-------------------+ +-------------------+ +-------------------+
^ | ^
| | |
| v |
+-------------------+ +-------------------+
| | | |
| MappedStatement | | TypeHandler |
| | | |
+-------------------+ +-------------------+
2.核心组件详解
1. SqlSessionFactoryBuilder
作用:SqlSessionFactoryBuilder 是 MyBatis 的入口点,负责解析配置文件并构建 SqlSessionFactory 实例。
源码关键代码:
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 解析 XML 配置文件
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 构建 Configuration 对象
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
// 创建 DefaultSqlSessionFactory 实例
return new DefaultSqlSessionFactory(config);
}
}
使用示例:
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
2. SqlSessionFactory
作用:SqlSessionFactory 是一个工厂接口,负责创建 SqlSession 实例。它是线程安全的,可以被多个线程共享。
核心方法:
openSession()
:创建一个新的 SqlSession 实例。openSession(boolean autoCommit)
:创建一个带有自动提交功能的 SqlSession。openSession(ExecutorType execType)
:创建一个指定执行器类型的 SqlSession。
源码关键代码:
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
// 其他重载方法...
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建执行器
final Executor executor = configuration.newExecutor(tx, execType);
// 创建 DefaultSqlSession 实例
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
}
3. SqlSession
作用:SqlSession 是 MyBatis 的核心接口,提供了执行 SQL 命令的方法。它是线程不安全的,应该在方法内部使用,用完后及时关闭。
核心方法:
selectOne(String statement, Object parameter)
:查询单个结果。selectList(String statement, Object parameter)
:查询多个结果。insert(String statement, Object parameter)
:插入数据。update(String statement, Object parameter)
:更新数据。delete(String statement, Object parameter)
:删除数据。commit()
:提交事务。rollback()
:回滚事务。getMapper(Class<T> type)
:获取 Mapper 接口的代理对象。
源码关键代码:
public interface SqlSession extends Closeable {
<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);
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();
void commit(boolean force);
void rollback();
void rollback(boolean force);
<T> T getMapper(Class<T> type);
Configuration getConfiguration();
Connection getConnection();
}
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
private final boolean autoCommit;
private boolean dirty;
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.autoCommit = autoCommit;
this.dirty = false;
}
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
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;
}
}
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
// 通过执行器执行更新操作
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public <T> T getMapper(Class<T> type) {
// 通过 Configuration 获取 Mapper 代理
return configuration.getMapper(type, this);
}
}
4. Executor
作用:Executor 是 MyBatis 的执行器,负责 SQL 语句的执行和缓存的维护。
主要实现类:
SimpleExecutor
:简单执行器,每次执行都会创建新的预处理语句。ReuseExecutor
:可重用执行器,会重用预处理语句。BatchExecutor
:批处理执行器,用于批量操作。CachingExecutor
:缓存执行器,用于二级缓存的管理。
源码关键代码:
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
int update(MappedStatement ms, Object parameter) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
List<BatchResult> flushStatements() throws SQLException;
void commit(boolean required) throws SQLException;
void rollback(boolean required) throws SQLException;
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
boolean isCached(MappedStatement ms, CacheKey key);
void clearLocalCache();
void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
Transaction getTransaction();
void close(boolean forceRollback);
boolean isClosed();
void setExecutorWrapper(Executor executor);
}
public abstract class BaseExecutor implements Executor {
// 实现 Executor 接口的方法
// 包含事务管理、缓存管理等通用逻辑
}
public class SimpleExecutor extends BaseExecutor {
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
// 准备语句
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行更新
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
@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);
}
}
}
5. Mapper 接口与映射文件
作用:Mapper 接口定义了数据库操作的方法,映射文件(或注解)定义了这些方法对应的 SQL 语句。
映射文件示例:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<select id="getUserById" parameterType="int" resultType="com.example.entity.User">
SELECT * FROM users WHERE id = #{id}
</select>
<insert id="insertUser" parameterType="com.example.entity.User">
INSERT INTO users (username, email, age)
VALUES (#{username}, #{email}, #{age})
</insert>
<!-- 其他 SQL 映射... -->
</mapper>
Mapper 接口示例:
package com.example.mapper;
import com.example.entity.User;
import java.util.List;
public interface UserMapper {
User getUserById(int id);
void insertUser(User user);
void updateUser(User user);
void deleteUser(int id);
List<User> getAllUsers();
}
MapperProxy 实现:
MyBatis 使用动态代理实现 Mapper 接口:
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -642454039855972983L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return methodCache.computeIfAbsent(method, m -> {
if (m.isDefault()) {
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
// 创建 MapperMethod 实例
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
}
3.核心组件协作流程
下面通过一个查询操作的时序图,展示 MyBatis 核心组件的协作流程:
Client SqlSession Executor MappedStatement JDBC
| | | | |
| getUserById(1) | | | |
|--------------------->| | | |
| | getMappedStatement("...") | |
| |---------------------------->| |
| | | | |
| | query(ms, 1) | | |
| |-------------->| | |
| | | getBoundSql() | |
| | |------------->| |
| | | | |
| | | prepareStatement() |
| | |----------------------------->|
| | | | Connection |
| | | |<-------------|
| | | | |
| | | executeQuery() |
| | |----------------------------->|
| | | | ResultSet |
| | |<-------------| |
| | | | |
| | | handleResultSets() |
| | |<-------------| |
| |<---------------| | |
|<---------------------| | | |
4.总结
通过深入剖析 MyBatis 的核心组件,我们可以看到其设计的精妙之处:
- 工厂模式:SqlSessionFactoryBuilder 构建 SqlSessionFactory,SqlSessionFactory 创建 SqlSession。
- 代理模式:MapperProxy 实现 Mapper 接口的动态代理,将方法调用转换为 SQL 执行。
- 策略模式:Executor 提供多种执行策略(SimpleExecutor、ReuseExecutor、BatchExecutor)。
- 模板方法模式:BaseExecutor 实现了通用的执行逻辑,具体实现由子类完成。
这种设计使得 MyBatis 既保持了灵活性,又提供了简单易用的 API。开发者可以通过配置文件或注解定义 SQL 映射,然后通过 Mapper 接口进行数据库操作,无需编写繁琐的 JDBC 代码。
在实际开发中,理解 MyBatis 的核心组件和工作原理,有助于我们更好地使用 MyBatis 进行开发,也能够在遇到问题时更快地定位和解决问题。