MyBatis 核心概念与实践指南:从代理模式到性能优化
1. 什么是 MyBatis?
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集的工作。
起源与发展:MyBatis 的前身是 Apache 的 iBatis 项目,2010年迁移至 Google Code 并更名为 MyBatis,2013年正式迁移至 Github。
核心思想:通过 XML 或注解来配置和映射原始类型、接口和 Java POJO (Plain Old Java Objects) 为数据库中的记录。
ORM 程度:MyBatis 属于半自动化 ORM 框架。
- O (Object):Java 实体类对象
- R (Relation):关系型数据库中的表
- M (Mapping):建立实体类属性与表字段之间的映射关系
为什么需要半自动化?
- 灵活性:对于复杂的多表关联查询、分页查询、复杂条件查询等,全自动 ORM(如 Hibernate)生成的 SQL 可能不够优化或非常复杂
- 性能:直接编写 SQL 可以更好地利用数据库的特性(如存储过程、特定函数的调用),有利于性能调优
2. 【代理模式】下开发 Mapper 层的规则
MyBatis 的代理模式使得开发者只需定义接口,MyBatis 会动态生成其实现类,极大简化了开发。
需要遵守的规则:
1. 接口与 XML 文件同名且在同一个包下
- `UserMapper.java` 接口与 `UserMapper.xml` 文件必须同名
- 位于相同的包目录结构中(Maven 项目通常将 xml 文件放在 resources 的对应目录下)
2. XML 中的 namespace 配置
- XML 文件中的 namespace 属性必须是接口的全限定名
- 示例:`namespace="com.example.dao.UserMapper"`
3. 方法名与 SQL ID 一致
- 接口中的方法名必须与 XML 文件中 SQL 语句的 id 属性值一致
4. 输入参数类型一致
- 接口方法的输入参数类型必须与 XML 中 SQL 语句的 parameterType 指定类型一致
- 使用 @Param 注解或多参数时可不指定 parameterType
5. 返回值类型一致
- 接口方法的返回值类型必须与 XML 中 SQL 语句的 resultType(或 resultMap)指定类型一致
- 返回集合时,resultType 指定的是集合中元素的类型
使用代理模式的好处:
- 代码更简洁:无需编写接口的实现类
- 类型安全:编译时就能发现错误
- 解耦:SQL 与 Java 代码完全分离,职责清晰
代理模式原理:MyBatis 通过 JDK 动态代理技术,在运行时为 Mapper 接口创建代理对象。当调用接口方法时,代理对象会拦截方法调用,根据方法名找到对应的 SQL 语句并执行。
3. 常用的【动态 SQL】标签
MyBatis 提供了丰富的动态 SQL 标签来处理复杂的查询条件:
1. if:条件判断,用于动态包含 WHERE 或 SET 子句的一部分
2. choose、when、otherwise:类似于 Java 中的 switch-case-default 语句
3. trim:自定义字符串的修剪(前缀、后缀)
4. where:智能生成 WHERE 子句,自动去除开头的 AND 或 OR
5. set:智能生成 SET 子句,用于更新语句
6. foreach:遍历集合,常用于 IN 条件或批量操作
7. bind:创建变量并绑定到上下文
8. sql:定义可重用的 SQL 代码片段
9. include:引用通过 sql 定义的代码片段
10. script:在注解中使用动态 SQL
foreach 标签详解:
```xml
<select id="selectUsersByIds" resultType="User">
SELECT FROM user
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
{id}
</foreach>
</select>
```
对应 Java 接口:
```java
List<User> selectUsersByIds(@Param("ids") List<Integer> idList);
```
foreach 属性:
- `collection`:要遍历的集合属性名
- `item`:每次遍历时生成的变量名
- `index`:遍历 List 或数组时的索引
- `open`:循环内容开始时的字符串
- `close`:循环内容结束时的字符串
- `separator`:每次循环之间的分隔符
动态 SQL 的好处:
- 灵活性:根据运行时条件动态构建 SQL
- 可维护性:条件判断逻辑写在 XML 中,与 Java 代码分离
- 减少代码冗余:避免字符串拼接组装 SQL
4. 【关联查询】:多表查询
MyBatis 处理多表关联查询主要有两种方式:
方式一:使用 ResultMap 进行关联映射
1. 编写多表联查的 SQL
2. 修改 POJO:在"一"的一方添加"多"的集合属性,在"多"的一方添加"一"的对象属性
3. 配置 ResultMap:使用 association 和 collection 标签
```xml
<resultMap id="userWithOrdersMap" type="User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<collection property="orders" ofType="Order">
<id property="id" column="order_id"/>
<result property="orderNumber" column="order_number"/>
</collection>
</resultMap>
<select id="selectUserWithOrders" resultMap="userWithOrdersMap">
SELECT
u.id AS user_id, u.name AS user_name,
o.id AS order_id, o.order_number
FROM user u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id = {userId}
</select>
```
方式二:使用 ResultType 映射到 VO/DTO
1. 编写多表联查的 SQL
2. 创建 VO/DTO:包含所有需要查询返回的字段
3. 配置 ResultType:直接映射到自定义的 VO 类
```java
public class UserOrderVO {
private Integer userId;
private String userName;
private Integer orderId;
private String orderNumber;
// getters & setters
}
```
```xml
<select id="selectUserOrderVO" resultType="com.example.vo.UserOrderVO">
SELECT
u.id AS userId, u.name AS userName,
o.id AS orderId, o.order_number AS orderNumber
FROM user u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id = {userId}
</select>
```
对比:
- ResultMap:更面向对象,适合关系复杂的领域模型映射
- VO/ResultType:更轻量灵活,适合定制化的查询结果
5. 延迟加载(懒加载)
概念:在关联查询时,先加载主对象,只有在程序真正需要使用关联对象时,才发起第二次 SQL 查询加载关联数据。
好处:
- 性能提升:减少不必要的数据库查询
- 降低数据库压力:加快初始查询速度
坏处:
- "N+1 查询问题":可能导致性能下降
- 调试困难:异常可能不会立即抛出
应用场景:适用于可能不需要立即加载的大对象
6. 查询缓存
MyBatis 提供了查询缓存来提高查询速度,减少数据库压力。
其他常见的数据库优化手段:
1. 数据库连接池 (如 HikariCP, Druid)
2. 使用索引:为查询条件字段建立合适的索引
3. 优化 SQL:避免 SELECT ,使用 LIMIT 分页
4. 应用层缓存:MyBatis 的查询缓存
MyBatis 缓存分为两级:
一级缓存:
- 范围:SqlSession 级别
- 状态:默认开启,无法关闭
- 失效时机:执行增删改操作、调用 clearCache() 或关闭 SqlSession
二级缓存:
- 范围:Mapper (Namespace) 级别
- 状态:默认关闭,需要手动配置开启
- 工作机制:SqlSession 关闭或提交后,一级缓存内容转存到二级缓存
开启二级缓存的步骤:
1. 核心配置文件开启全局开关:
```xml
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
```
2. 在具体的 Mapper.xml 中配置:
```xml
<cache/>
```
3. POJO 实现 Serializable 接口
使用第三方缓存(如 Ehcache):
1. 添加依赖:mybatis-ehcache
2. 配置使用 Ehcache:
```xml
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
```
3. 配置 ehcache.xml(可选)
缓存选择建议:
- 读多写少且数据实时性要求不高的场景:使用二级缓存
- 读写频繁或数据实时性要求高的场景:关闭二级缓存
总结
MyBatis 作为一个半自动化的 ORM 框架,在灵活性和性能之间找到了良好的平衡点。通过掌握代理模式下的开发规则、动态 SQL 的使用、关联查询的处理方式以及缓存机制,可以充分发挥 MyBatis 的优势,构建高效、可维护的数据访问层。
在实际项目中,应根据具体业务需求选择合适的查询方式和缓存策略,在保证数据一致性的前提下提升系统性能。