以下内容是关于 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>
查询语句
xml
<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>
xml
<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>
:手动生成主键(适用于不支持自增的数据库)
xml
<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 片段
xml
<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 属性名
xml
<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>
:普通字段映射。
高级用法:关联映射(一对一、一对多)
xml
<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>
:鉴别器(多态映射)
xml
<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 注入"),欢迎继续提问,我可以给出更具体的代码示例。