MyBatis通过解析SQL Mapper XML文件来构建映射语句,任何格式或配置上的偏差都会导致解析失败或运行时绑定异常。理解这些常见错误点,是快速定位问题的关键。

一、DTD声明与MyBatis版本不匹配
XML文件头部的DOCTYPE声明决定了解析器按哪种规范校验文件。如果引入了错误版本的DTD,或者直接从旧项目拷贝而未更新命名空间,解析阶段就会报未知元素错误。
例如MyBatis 3.x应使用如下声明,若写成ibatis的旧地址则无法识别<mapper>中的新标签:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://ipipp.com/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.demo.UserMapper"> </mapper>
建议不要手写DOCTYPE,而是从官方文档或新建模块中复制。同时注意网络受限环境下,IDE可能无法校验远程DTD,可配置本地catalog避免误报。
二、namespace属性未正确设置或写错
每个映射文件的根<mapper>标签必须有namespace,它对应接口全限定名。写错会导致Mapper接口找不到对应XML,抛出Invalid bound statement异常。
常见错误包括多写空格、少写包路径、使用了别名而非全名。正确写法应和Java接口保持一致:
<mapper namespace="com.demo.mapper.UserMapper">
<select id="selectById" resultType="com.demo.entity.User">
select * from user where id = #{id}
</select>
</mapper>
当项目启用包扫描时,namespace还必须和扫描路径下的接口名精确匹配,否则即使XML放在resources目录也不会被绑定。
三、SQL语句id与接口方法名不一致
XML中定义的<select>、<insert>等标签的id值,必须和Mapper接口的方法名完全相同。大小写敏感,多一个下划线都会让绑定失效。
如下接口方法为listByAge,但XML中写成list_by_age,调用时就会找不到:
<select id="list_by_age" resultType="com.demo.entity.User">
select * from user where age = #{age}
</select>
养成在接口写完后立即编写XML的习惯,并利用IDE的MyBatis插件做双向跳转检查,能大幅减少此类低级错误。
四、resultType与resultMap混用或写错
resultType用于自动映射简单类型或POJO,resultMap引用已定义的映射规则。初学者常把类名写成不存在的路径,或在同一标签同时写两个属性。
若实体类在com.demo.entity包下,应写全限定名或配置typeAlias。以下写法会因找不到类而解析报错:
<select id="selectAll" resultType="User"> select * from user </select>
应在配置文件中注册别名,或改为resultType="com.demo.entity.User"。若字段与属性名不同,则必须使用resultMap而非依赖自动映射。
五、动态SQL标签未正确闭合
MyBatis的动态SQL如<if>、<foreach>、<choose>都要求严格闭合。遗漏结束标签会造成XML格式错误,解析器直接失败。
下面代码缺少</if>结束符,启动即报XML格式异常:
<select id="query" resultType="com.demo.entity.User">
select * from user
<if test="name != null">
where name = #{name}
</select>
使用IDE的XML格式化功能可快速发现缩进异常,也可以在编写时每开一个动态标签就立刻补上闭合标签。
六、特殊字符未转义
SQL中的小于号、大于号属于XML保留字符。直接写在标签体内会破坏结构,比如where age < 20必须转义为<。
错误示例与正确示例如下:
<!-- 错误:未转义 --> <select id="lt" resultType="com.demo.entity.User"> select * from user where age < 20 </select> <!-- 正确:使用CDATA或转义 --> <select id="lt" resultType="com.demo.entity.User"> select * from user where age < 20 </select>
对于大段含特殊字符的SQL,推荐使用<![CDATA[ ... ]]>包裹,避免逐个转义带来的阅读困难。
七、参数占位符使用错误
MyBatis中#{}表示预编译参数,${}表示字符串拼接。误用${}拼接用户输入会引发注入,且若参数名不对也会解析出错。
当接口方法使用@Param注解时,XML中必须对应名称:
<select id="find" resultType="com.demo.entity.User">
select * from user where name = #{username}
</select>
如果方法签名为find(@Param("username") String name),而XML写#{name}就会取不到值。多参数时建议统一用@Param明确命名。
八、SQL片段引用错误
通过<sql>定义可复用片段,用<include>引用。refid写错或片段不在同一namespace下都会导致解析异常。
正确方式如下:
<sql id="cols">id, name, age</sql> <select id="select" resultType="com.demo.entity.User"> select <include refid="cols"/> from user </select>
跨文件引用需写全namespace.sqlid,例如refid="com.demo.CommonSQL.cols",否则解析器报找不到片段定义。
九、文件未被构建工具打包
Maven默认只编译resources下的.xml,若Mapper XML放在src/main/java目录且未配置资源拷贝,运行时会因缺文件而绑定失败。
在pom.xml中增加资源包含规则:
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
也可统一将XML移至resources对应包路径,避免构建配置复杂化,这是更推荐的项目结构。
十、重复id或重复加载同一文件
同一namespace下不允许两个标签id相同,否则后加载的覆盖前者或直接报错。多模块项目里若拷贝了同名XML也容易造成冲突。
检查方式包括全局搜索id值,以及观察启动日志中Loaded Mapper提示。示例冲突:
<select id="get" resultType="com.demo.entity.User">
select * from user where id=#{id}
</select>
<select id="get" resultType="com.demo.entity.User">
select * from user2 where id=#{id}
</select>
规范命名和模块职责划分能从根源避免重复。团队可借助CI脚本扫描XML中id唯一性作为质量门禁。
mybatisSQL_Mapper_XMLXML解析修改时间:2026-08-10 04:30:35