MyBatis Mapper XML 核心详解
以下内容是关于 MyBatis 框架中 Mapper XML 文件 的官方文档或技术说明,内容非常全面。您问“怎么理解”,我将为您系统地、通俗地解析这份文档的核心要点,帮助您掌握 MyBatis 的核心思想和关键组件。
一、核心思想:MyBatis 的设计哲学
“The true power of MyBatis is in the Mapped Statements.”
(MyBatis 的真正力量在于“映射语句”)
MyBatis 是一个 半自动化的 ORM(对象关系映射)框架,它不像 Hibernate 那样完全隐藏 SQL,而是:
- 让你写 SQL
- 帮你处理参数设置和结果映射
- 极大减少 JDBC 冗余代码(文档说节省 95%)
它不试图“取代 SQL”,而是“增强 SQL 的能力”。
二、Mapper XML 的核心元素(按推荐顺序)
元素 | 说明 |
---|---|
<cache> | 配置当前命名空间的二级缓存 |
<cache-ref> | 引用其他命名空间的缓存配置 |
<resultMap> | 最强大、最复杂的元素,定义数据库结果集如何映射到 Java 对象 |
<sql> | 可重用的 SQL 片段 |
<insert> | 插入语句 |
<update> | 更新语句 |
<delete> | 删除语句 |
<select> | 查询语句 |
⚠️ 注意:
<parameterMap>
已废弃,不再使用。
三、核心元素详解
1. <select>
查询语句
<select id="selectPerson" parameterType="int" resultType="hashmap">SELECT * FROM PERSON WHERE ID = #{id}
</select>
id
:在当前命名空间内唯一标识这个 SQL。parameterType
:传入参数的类型(可选,MyBatis 能自动推断)。resultType
:返回结果的类型(如User
、HashMap
等)。#{id}
:预编译参数,防止 SQL 注入,等价于 JDBC 的?
。
📌 重要属性
属性 | 说明 |
---|---|
resultMap | 引用外部 <resultMap> ,用于复杂映射(和 resultType 二选一) |
useCache | 是否使用二级缓存(默认 true ) |
flushCache | 是否清空缓存(默认 false ) |
timeout | 查询超时时间(秒) |
fetchSize | 提示数据库每次返回多少行数据(优化大数据量查询) |
2. <insert>
、<update>
、<delete>
<insert id="insertAuthor">INSERT INTO Author (username, password, email) VALUES (#{username}, #{password}, #{email})
</insert>
🔑 特殊属性(仅 insert/update)
属性 | 说明 |
---|---|
useGeneratedKeys="true" | 使用数据库自增主键(如 MySQL) |
keyProperty="id" | 将生成的主键值赋给 Java 对象的 id 属性 |
keyColumn="ID" | 指定数据库中生成主键的列名(PostgreSQL 等需要) |
🌟 <selectKey>
:手动生成主键(适用于不支持自增的数据库)
<insert id="insertAuthor"><selectKey keyProperty="id" resultType="int" order="BEFORE">SELECT CAST(RANDOM()*1000000 AS INTEGER) FROM SYSIBM.SYSDUMMY1</selectKey>INSERT INTO Author (id, username) VALUES (#{id}, #{username})
</insert>
order="BEFORE"
:先生成主键再插入。order="AFTER"
:先插入再查主键(如 Oracle 的RETURNING
或CURRVAL
)。
3. <sql>
:可重用 SQL 片段
<sql id="userColumns">${alias}.id, ${alias}.username, ${alias}.password
</sql><select id="selectUsers" resultType="map">SELECT <include refid="userColumns"><property name="alias" value="t1"/></include>,<include refid="userColumns"><property name="alias" value="t2"/></include>FROM users t1 CROSS JOIN users t2
</select>
<include>
引用 SQL 片段。${alias}
是字符串替换(注意 SQL 注入风险)。
4. 参数处理:#{}
vs ${}
语法 | 说明 | 安全性 | 用途 |
---|---|---|---|
#{param} | 预编译参数,使用 ? 占位符 | ✅ 安全 | 一般参数(如 WHERE id = #{id} ) |
${param} | 字符串替换,直接拼接 SQL | ❌ 有 SQL 注入风险 | 动态表名、列名(如 ORDER BY ${column} ) |
✅ 最佳实践:
- 用户输入的列名/表名,必须做白名单校验。
- 能用
#{}
就不用${}
。
5. <resultMap>
:最强大的映射工具
场景:当数据库字段名 ≠ Java 属性名
<resultMap id="userResultMap" type="User"><id property="id" column="user_id"/><result property="username" column="user_name"/><result property="password" column="hashed_password"/>
</resultMap><select id="selectUsers" resultMap="userResultMap">SELECT user_id, user_name, hashed_password FROM users WHERE id = #{id}
</select>
<id>
:映射主键,有助于性能优化。<result>
:普通字段映射。
高级用法:关联映射(一对一、一对多)
<resultMap id="detailedBlogResultMap" type="Blog"><id property="id" column="blog_id"/><result property="title" column="blog_title"/><!-- 一对一:博客属于一个作者 --><association property="author" javaType="Author"><id property="id" column="author_id"/><result property="username" column="author_username"/></association><!-- 一对多:博客有多个文章 --><collection property="posts" ofType="Post"><id property="id" column="post_id"/><result property="subject" column="post_subject"/></collection>
</resultMap>
association
:一对一关联(如 Blog → Author)collection
:一对多关联(如 Blog → List)ofType
:集合中元素的类型(相当于泛型)
🌟 <discriminator>
:鉴别器(多态映射)
<discriminator javaType="int" column="draft"><case value="1" resultType="DraftPost"/><case value="0" resultType="PublishedPost"/>
</discriminator>
根据 draft
字段的值,决定使用哪个 resultType
,实现“一个查询返回不同子类”。
四、总结:如何理解这份文档?
维度 | 理解 |
---|---|
定位 | MyBatis 不是“全自动 ORM”,而是“SQL 增强器” |
核心 | <select> + <resultMap> 解决了 JDBC 最繁琐的部分 |
优势 | 写 SQL 自由,映射灵活,性能可控 |
风险 | 使用 ${} 时需警惕 SQL 注入 |
最佳实践 | 从小 resultMap 开始,逐步构建复杂映射,配合单元测试 |
五、学习建议
- 先掌握简单映射:
resultType
+ 别名 +#{}
参数 - 再学
<resultMap>
:处理字段名不一致 - 再学关联映射:
association
和collection
- 最后学高级特性:
<discriminator>
、缓存、动态 SQL
如果您有具体的使用场景(比如“如何实现一对多查询”或“如何防止 SQL 注入”),欢迎继续提问,我可以给出更具体的代码示例。