Appearance
MyBatis
一、为什么需要 MyBatis?
原生 JDBC 操作数据库需要手写大量样板代码:加载驱动、创建连接、拼接 SQL、处理 ResultSet、关闭资源。SQL 硬编码在 Java 中,修改 SQL 需要重新编译。
MyBatis 将 SQL 从 Java 代码中分离到 XML 或注解中,自动处理参数绑定和结果映射,让你专注于 SQL 本身。相比于 JPA/Hibernate 的全自动 ORM,MyBatis 是半自动——你完全掌控 SQL,适合复杂查询和性能优化场景。
二、核心架构
┌─────────────────────────────────────────┐
│ MyBatis 核心 │
├─────────────────────────────────────────┤
│ SqlSessionFactory(全局唯一,线程安全) │
│ │ │
│ ▼ │
│ SqlSession(一次会话,非线程安全) │
│ │ │
│ ▼ │
│ Executor(执行器) │
│ ├── SimpleExecutor(默认) │
│ ├── ReuseExecutor(复用 Statement) │
│ └── BatchExecutor(批量操作) │
│ │ │
│ ┌────┴────┐ │
│ ▼ ▼ │
│ MappedStatement Configuration │
│ (封装 SQL) (全局配置) │
└─────────────────────────────────────────┘执行流程:
- 读取配置文件,创建
SqlSessionFactory - 从
SqlSessionFactory获取SqlSession SqlSession调用Executor执行 SQLExecutor从MappedStatement获取 SQL 和参数映射- 通过
TypeHandler处理参数和结果 - 返回映射后的 Java 对象
三、核心配置
3.1 XML 映射
xml
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<select id="findById" resultType="com.example.entity.User">
SELECT id, name, email, created_at
FROM users
WHERE id = #{id}
</select>
</mapper>java
// UserMapper.java
public interface UserMapper {
User findById(Long id);
}3.2 #{} vs ${}
| #{} | ${} | |
|---|---|---|
| 处理方式 | 预编译占位符 ? | 字符串替换 |
| SQL 注入 | 安全 | 危险 |
| 适用场景 | 参数值 | 表名、列名、ORDER BY |
xml
<!-- #{}:预编译,安全 -->
SELECT * FROM users WHERE id = #{id}
→ SELECT * FROM users WHERE id = ?
<!-- ${}:字符串替换,防 SQL 注入 -->
SELECT * FROM ${tableName} WHERE id = #{id} <!-- tableName 不能传用户输入 -->为什么 #{} 能防止 SQL 注入? 预编译时 SQL 结构已确定,? 占位符只接受参数值,恶意 SQL 片段被视为字符串而非 SQL 指令。
3.3 动态 SQL
xml
<select id="findByCondition" resultType="User">
SELECT * FROM users
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="email != null">
AND email = #{email}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
</select>| 标签 | 作用 |
|---|---|
<where> | 自动去掉第一个 AND/OR |
<if> | 条件判断 |
<choose>/<when>/<otherwise> | 多选一(类似 switch) |
<foreach> | 遍历集合(IN 查询) |
<set> | UPDATE 时自动去掉多余逗号 |
<trim> | 自定义截取 |
四、缓存机制
4.1 一级缓存(SqlSession 级别)
原理: 同一个 SqlSession 内,相同查询只执行一次 SQL,后续从缓存取。默认开启,无法关闭。
java
SqlSession session = factory.openSession();
User u1 = session.selectOne("findById", 1L); // 查数据库
User u2 = session.selectOne("findById", 1L); // 从缓存取,u1 == u2 为 true
session.close();一级缓存失效场景: 执行了 insert/update/delete(清空缓存)、手动 clearCache()、不同 SqlSession。
4.2 二级缓存(Mapper 级别)
原理: 跨 SqlSession 共享,基于 namespace。需显式开启:
xml
<!-- 1. 全局配置 -->
<setting name="cacheEnabled" value="true"/>
<!-- 2. Mapper 中声明 -->
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>注意: 二级缓存存的是序列化后的数据,查询时反序列化返回新对象,不是同一个引用。多表关联查询缓存失效问题严重,生产环境谨慎使用。
五、插件机制
原理: MyBatis 通过责任链模式拦截四大对象的方法调用:
| 拦截对象 | 可拦截方法 |
|---|---|
| Executor | update, query, commit, rollback |
| StatementHandler | prepare, parameterize, batch, update, query |
| ParameterHandler | getParameterObject, setParameters |
| ResultSetHandler | handleResultSets, handleOutputParameters |
java
// 分页插件示例(简化)
@Intercepts(@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class PageInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 修改 SQL,添加 LIMIT 子句
// 执行修改后的 SQL
// 返回分页结果
return invocation.proceed();
}
}六、与 Spring Boot 集成
yaml
mybatis:
mapper-locations: classpath:mapper/*.xml # XML 映射文件位置
type-aliases-package: com.example.entity # 实体类包
configuration:
map-underscore-to-camel-case: true # 下划线转驼峰
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl七、面试要点
Q:MyBatis 和 JPA/Hibernate 的区别?
MyBatis 是半自动 ORM,SQL 由开发者完全控制,灵活但工作量大;Hibernate 是全自动 ORM,自动生成 SQL,开发快但复杂查询优化困难。
Q:#{} 和 ${} 的区别?
#{} 是预编译占位符,安全防注入;${} 是字符串替换,有注入风险,仅用于动态表名/列名。
Q:MyBatis 一级缓存和二级缓存的区别?
一级缓存 SqlSession 级别,默认开启,同一 Session 内相同查询只查一次;二级缓存 Mapper 级别,跨 Session 共享,需显式开启,序列化存储。
Q:MyBatis 插件原理?
基于动态代理和责任链模式,拦截 Executor、StatementHandler、ParameterHandler、ResultSetHandler 四大对象的方法。分页插件(PageHelper)就是通过拦截 Executor.query() 改写 SQL 实现。
