MyBatis 的 XML 映射文件是连接 Java 接口与 SQL 语句的核心配置。一个映射文件通常包含 SQL 定义、参数映射和结果映射三部分,文件开头需要声明 MyBatis 的 DTD 约束,并在 mapper 根节点中指定命名空间。命名空间最好与对应的 Mapper 接口全限定名保持一致,这样 MyBatis 才能通过接口方法名直接找到对应的 SQL 语句。下面从一个最简单的用户查询映射文件开始拆解。

在 resources 目录的 mapper 子目录中新建 UserMapper.xml,先写入 XML 声明和 DOCTYPE。MyBatis 官方推荐的 DTD 声明能够在编写 XML 时提供标签提示,也能让解析器在启动阶段校验标签是否合法。mapper 根节点的 namespace 属性不能省略,它相当于这个映射文件的唯一标识。开发中常见的做法是让 namespace 等于 Mapper 接口的全限定名,例如 com.example.mapper.UserMapper。
一、映射文件的基础结构与命名空间规则
一个规范的 MyBatis 映射文件必须包含 XML 声明、DOCTYPE 声明、mapper 根节点三块。DOCTYPE 中引用的 DTD 文件来自 MyBatis 官方站点,它定义了 select、insert、update、delete、resultMap、sql 等标签的合法结构。如果 DOCTYPE 写错或者缺失, MyBatis 在解析 XML 时可能不会报错,但标签内的小问题会变得难以排查。
mapper 标签的 namespace 属性是整个文件的门牌号。MyBatis 规定每个 namespace 在全局配置中必须唯一,否则启动时会抛出解析异常。当 namespace 与 Mapper 接口的完全限定名一致时,MyBatis 会自动将接口方法与映射文件中的语句 ID 做匹配。例如接口中定义了 User selectById(Integer id),那么 XML 中必须存在一个 id 为 selectById 的 select 节点,并且参数类型和返回类型要能对应上。
文件结构参考如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectById" resultType="com.example.entity.User">
SELECT * FROM `user` WHERE id = #{id}
</select>
</mapper>
注意 XML 中对小于号和大于号的处理。如果 SQL 中需要写 age < 30 这样的条件,必须写成 < 30,否则 XML 解析器会把小于号当成标签起始符。同理,大于号虽然没有小于号那么敏感,但建议统一使用 > 和 < 来保证可读性和兼容性。
二、四类 SQL 语句的配置与参数传递
MyBatis 中最常用的 SQL 节点是 select、insert、update、delete。四类节点都支持 id、parameterType、timeout 等属性,但 select 还需要配置 resultType 或 resultMap 来告诉 MyBatis 查询结果应该映射成什么 Java 类型。insert 节点经常配合 useGeneratedKeys 和 keyProperty 实现自增主键回填,update 和 delete 则主要关注影响行数。
参数占位符是初学者最容易混淆的地方。MyBatis 提供了 #{} 和 ${} 两种写法。#{} 会被 MyBatis 解析为预编译占位符 ?,最终交给 PreparedStatement 处理,能够有效防止 SQL 注入。${} 则直接进行字符串拼接,只有在需要动态拼表名、排序字段等无法预编译的场景才应该使用它。任何来自用户输入的内容都不要使用 ${},否则恶意输入可能改变 SQL 语义。
下面演示一个包含新增、修改、删除和条件查询的完整映射:
<insert id="insertUser" parameterType="com.example.entity.User"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO `user`(username, email, age)
VALUES(#{username}, #{email}, #{age})
</insert>
<update id="updateUser" parameterType="com.example.entity.User">
UPDATE `user`
SET username = #{username},
email = #{email},
age = #{age}
WHERE id = #{id}
</update>
<delete id="deleteById" parameterType="int">
DELETE FROM `user` WHERE id = #{id}
</delete>
<select id="selectByCondition" parameterType="map" resultType="com.example.entity.User">
SELECT id, username, email, age
FROM `user`
WHERE age >= #{minAge}
AND age <= #{maxAge}
</select>
parameterType 可以写 int、string、map,也可以写全限定类名。实际上 MyBatis 对 parameterType 并不强制要求,大多数情况下不写也能通过参数对象的类型自动推断。但如果参数是简单类型,比如单个 int、String,建议写上 parameterType,这样 XML 可读性更好,也能减少一部分推断开销。当方法有多个参数且没有用 @Param 注解时,MyBatis 会用 arg0、arg1、param1、param2 等默认名称,容易出错,更推荐使用 @Param 给每个参数起名。
三、动态 SQL:if、where、foreach、trim 的实战组合
动态 SQL 是 MyBatis XML 映射文件最具价值的特性之一。在传统 JDBC 中,要根据条件拼接 SQL 十分繁琐,需要手动处理 WHERE 和 AND 的拼接顺序,还要防止出现多余的 AND 或 OR。MyBatis 提供了 if、where、set、foreach、trim 等标签,把条件拼接逻辑放进了 XML 中,既能保持 SQL 语句的直观,又能避免拼接错误。
if 标签配合 where 标签可以处理常见的非空条件查询。where 标签会自动去除开头多余的 AND 或 OR,并在内部至少成立一个条件时补上 WHERE 关键字。如果所有条件都不成立,where 标签不会输出 WHERE,避免产生 SELECT * FROM user WHERE 这样的非法语句。set 标签则适用于 update 语句,可以自动去掉最后多余的逗号。
下面是一个条件组合查询和批量删除的示例:
<select id="selectByUser" parameterType="com.example.entity.User"
resultType="com.example.entity.User">
SELECT * FROM `user`
<where>
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
ORDER BY id DESC
</select>
<delete id="deleteByIds" parameterType="list">
DELETE FROM `user`
WHERE id IN
<foreach collection="list" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</delete>
foreach 标签除了处理 IN 查询,还能实现批量插入。collection 属性指定被遍历的集合,item 表示每次遍历的元素名称。当参数是数组时,collection 可以写 array;当参数是 List 时,可以写 list;如果用了 @Param 注解,就必须写 @Param 指定的名称。open、separator、close 分别控制遍历内容的前缀、分隔符和后缀。
动态更新时经常使用 set 标签:
<update id="updateUserSelective" parameterType="com.example.entity.User">
UPDATE `user`
<set>
<if test="username != null">username = #{username},</if>
<if test="email != null">email = #{email},</if>
<if test="age != null">age = #{age},</if>
</set>
WHERE id = #{id}
</update>
如果不使用 set 标签,当最后一个 if 条件不成立时,前面的字段更新语句会多一个逗号,导致 SQL 语法错误。set 标签能自动删除结尾多余的逗号,让动态更新代码更加健壮。trim 标签则更为灵活,可以用 prefix、suffix、prefixOverrides、suffixOverrides 组合出 where、set 等价的功能,适合复杂场景。
四、结果映射:resultType 与 resultMap 的选择
查询节点必须明确结果如何映射到 Java 对象。resultType 适用于数据库字段名与 Java 属性名一致或满足驼峰命名自动转换的场景。例如数据库列 user_name 对应 Java 属性 userName,如果在全局配置中开启了 mapUnderscoreToCamelCase,就可以直接使用 resultType。否则需要使用别名或者 resultMap 手动映射。
resultMap 的功能比 resultType 强得多,它不仅解决字段名不一致问题,还能描述一对一、一对多等复杂对象关系。在 resultMap 中,id 子标签用于标记主键字段,result 子标签用于普通字段,association 处理单个关联对象,collection 处理集合属性。对于简单查询,优先用 resultType,代码量更少;当出现嵌套对象或字段映射复杂时,再引入 resultMap。
<resultMap id="UserResultMap" type="com.example.entity.User">
<id column="id" property="id" />
<result column="user_name" property="userName" />
<result column="email" property="email" />
<result column="age" property="age" />
</resultMap>
<select id="selectById" parameterType="int" resultMap="UserResultMap">
SELECT id, user_name, email, age
FROM `user`
WHERE id = #{id}
</select>
映射文件写完后,还需要在 MyBatis 主配置文件中注册 mapper。可以通过 resource 属性指定 XML 文件位置,也可以通过 package 扫描整个接口包。如果同时使用 XML 和注解,建议保持映射文件与接口在同一目录结构下,并在全局配置中统一加载。配置完成后启动项目,调用接口方法即可验证 SQL 是否正确执行。遇到绑定异常时,优先检查 namespace 是否与接口全限定名一致,以及 select 节点的 id 是否与接口方法名完全相同。
SQL 片段复用也是 XML 映射文件的实用技巧。通过 sql 节点定义列名、条件等公共部分,再使用 include 节点引入,可以减少重复配置。比如把用户表常用列抽成 user_base_column,多个 select 节点都能复用,修改列名时只需要改一处。
<sql id="user_base_column"> id, user_name, email, age </sql> <select id="selectSimpleList" resultType="com.example.entity.User"> SELECT <include refid="user_base_column" /> FROM `user` ORDER BY id DESC </select>
掌握这些 XML 配置技巧后,再结合 parameterType、resultMap 和动态 SQL 标签,已经能够覆盖绝大多数 MyBatis 开发场景。建议在每次修改映射文件后都启动一次单元测试,尽早发现 XML 转义、参数名不匹配、resultMap 配置遗漏等问题,避免问题堆积到联调阶段。
MyBatis映射文件SQL语句配置MyBatis动态SQL修改时间:2026-09-21 21:30:01