在之前的系列文章中,我们一步步地实现了 MyBatis 框架的核心功能。本篇文章作为该系列的第 94 篇,将对整个架构进行一次完整的回顾,并深入解析其中一些关键技术点,希望能帮助读者更好地理解 MyBatis 的设计思想和实现细节。在实际项目中,数据库连接管理通常由连接池(如 HikariCP 或 Druid)负责,并通过 Nginx 进行反向代理和负载均衡,以应对高并发访问,必要时还会使用宝塔面板简化运维操作。
MyBatis 整体架构回顾
MyBatis 的整体架构可以概括为以下几个核心模块:
- 配置解析模块:负责解析 MyBatis 的配置文件(mybatis-config.xml)和 Mapper 文件,将 XML 配置转换为 Java 对象,存储到 Configuration 对象中。
- SqlSession 管理模块:负责创建和管理 SqlSession 对象,SqlSession 是 MyBatis 对外提供的操作数据库的接口。
- Executor 执行器模块:负责执行 SQL 语句,包括 StatementHandler、ParameterHandler 和 ResultSetHandler 等。
- StatementHandler 模块:负责将 SQL 语句转换为 JDBC Statement 对象,并设置参数。
- ParameterHandler 模块:负责将 Java 对象转换为 SQL 语句需要的参数。
- ResultSetHandler 模块:负责将 JDBC ResultSet 结果集转换为 Java 对象。
- TypeHandler 类型处理器模块:负责 Java 类型和 JDBC 类型之间的转换。
- Mapper 接口代理模块:通过动态代理机制,将 Mapper 接口的方法调用转换为 SQL 语句的执行。
核心技术深度解析
1. 配置解析模块
MyBatis 的配置解析模块使用了 XML 解析器(如 DOM4J 或 JAXB)来解析 XML 配置文件。解析后的配置信息会被存储到 Configuration 对象中,Configuration 对象包含了数据源、事务管理器、Mapper 映射等信息。例如,解析数据源配置的代码如下:
// 解析数据源配置
private void parseDataSource(XNode context) throws Exception {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance(); // 反射创建数据源工厂
factory.setProperties(props);
DataSource dataSource = factory.getDataSource();
builderAssistant.environment(id, transactionManagerType, dataSource);
}
2. Executor 执行器模块
Executor 是 MyBatis 的核心组件之一,负责执行 SQL 语句。MyBatis 提供了多种 Executor 实现,如 SimpleExecutor、ReuseExecutor 和 BatchExecutor。SimpleExecutor 每次执行 SQL 语句都会创建一个新的 Statement 对象,ReuseExecutor 会重用 Statement 对象,BatchExecutor 会将多个 SQL 语句批量执行。选择合适的 Executor 实现可以提高 SQL 执行的效率。
// SimpleExecutor 的 execute 查询方法
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List<E> list;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 创建 StatementHandler
stmt = prepareStatement(handler, ms.getStatementLog()); // 预编译 statement
list = handler.query(stmt, resultHandler); // 执行查询
} finally {
closeStatement(stmt);
}
return list;
}
3. Mapper 接口代理
MyBatis 使用动态代理技术,将 Mapper 接口的方法调用转换为 SQL 语句的执行。当调用 Mapper 接口的方法时,MyBatis 会创建一个代理对象,该代理对象会拦截方法调用,并根据方法名和参数信息,找到对应的 SQL 语句,并执行该 SQL 语句。例如:
// MapperProxy 的 invoke 方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
实战避坑经验总结
- SQL 注入风险:在使用 MyBatis 时,要避免 SQL 注入风险。可以使用
#来占位,而不是$。#会使用 PreparedStatement,可以有效地防止 SQL 注入。 - N+1 查询问题:在使用 MyBatis 的关联查询时,可能会出现 N+1 查询问题。可以使用
fetchType="lazy"来延迟加载关联对象,或者使用join查询来避免 N+1 查询问题。 - 缓存问题:MyBatis 提供了两级缓存:一级缓存和二级缓存。一级缓存是 SqlSession 级别的缓存,二级缓存是 Mapper 级别的缓存。要合理地使用缓存,避免脏数据问题。可以通过设置
useCache="true"开启二级缓存。 - 事务管理:要确保事务的正确性,需要配置合适的事务管理器。可以使用 JDBC 事务管理器或 Spring 事务管理器。
总结
通过手写 MyBatis 框架,我们可以深入理解 MyBatis 的设计思想和实现细节。掌握 MyBatis 的核心技术,可以帮助我们更好地使用 MyBatis,并解决实际项目中遇到的问题。希望本篇文章能够帮助读者更好地理解 MyBatis 框架。
冠军资讯
代码一只喵