写过MyBatis映射文件的开发者几乎都遇到过同一个问题:在SQL里用到<或者>符号时,项目启动直接报错,提示The content of elements must consist of well-formed character data。这不是SQL写错了,而是XML解析器把这两个符号当成了标签的起始符。本文围绕这一高频问题,系统地讲解几种正确写法、各自的适用场景以及容易踩的坑。

一、为什么大于小于号在MyBatis的XML里会报错
MyBatis的Mapper本质是一个XML文件,而XML规范中,<被保留用于标签起始,&被保留用于实体引用起始。当SQL语句中出现age < 18这样的写法时,解析器会把< 18当成一个新标签的开始,读到后面的空格或非法字符时就会抛出解析异常。
值得注意的是,>单独使用其实在大多数解析器里是容忍的,因为规范并未严格禁止它出现在文本内容中,但一旦形成>=或者与其他字符组合,仍可能出现兼容性问题。所以工程实践中通常要求大于号和小于号统一处理,保持写法一致,避免留下隐患。
理解了根源,解决思路也就清晰了:要么让解析器不把这些字符当成标记,要么换成解析器认识的合法写法。这对应了下面两种主流方案。
二、方案一:使用XML标准转义符
最常见的做法是把特殊字符替换成XML预定义实体,对应关系如下:
- 小于:
<替换为< - 大于:
>替换为> - 小于等于:
<=替换为<= - 大于等于:
>=替换为>= - 不等于:
<>替换为<>,也可以用!=规避 - 与符号:
&替换为&
实际写法示例:
<select id="selectByAgeRange" resultType="User">
SELECT * FROM t_user
WHERE age >= #{minAge}
AND age <= #{maxAge}
AND status <> 'DELETED'
</select>这种写法的好处是完全符合XML规范,与动态SQL标签可以自由混用,不会产生任何冲突。缺点是SQL的可读性下降,特别是条件复杂的语句里满屏的转义符,Code Review时不太直观。另外要注意转义必须成对完整,漏掉分号写成<同样会解析失败。
三、方案二:使用CDATA区段包裹SQL
另一种方案是用CDATA区段,被<![CDATA[ ]]>包裹的内容会被解析器原样保留,其中的大于小于号无需任何处理:
<select id="selectByScore" resultType="ScoreVO">
SELECT * FROM t_exam
WHERE score <![CDATA[ >= 60 AND score <= 90 ]]>
</select>这种写法最大的优点是SQL保持了原始面貌,一眼就能看懂条件逻辑,很多团队在报表类复杂查询里偏好这种方式。但它有一个致命的限制:CDATA内部的内容不会被解析,也就是说<if>、<where>、<foreach>这些动态SQL标签绝对不能放在CDATA里面,否则会被当成纯文本原样输出到SQL中,直接引发语法错误。
一个常见的错误示例:
<!-- 错误示范:if标签被CDATA包住,动态条件失效 -->
<select id="wrongExample" resultType="User">
SELECT * FROM t_user
WHERE 1=1
<![CDATA[
<if test="name != null">AND name = #{name}</if>
AND age > 18
]]>
</select>正确的做法是只把包含比较符号的那一小段SQL放进CDATA,动态标签留在CDATA外面,两者拼接使用。例如把AND age > 18单独用CDATA包住,而<if test="name != null">正常书写在外部。
四、常见问题与注意事项
第一,注解方式写SQL不需要转义。如果用的是@Select注解把SQL直接写在Java字符串里,那么大于小于号就是普通字符,既不需要转义也不需要CDATA,因为注解内容不经过XML解析。部分初学者把XML里的转义习惯带到注解里,反而写出了>=这样的字面量导致SQL语法错误。
第二,<if>的test属性中的比较不需要转义。test属性里的表达式是OGNL表达式而不是XML文本,例如test="age != null and age > 0"中的>写在属性值里通常也能正常工作,但为了规范和兼容性,推荐统一写成>对应的转义形式>,二者在主流版本中都被支持。
第三,模糊查询里的特殊字符要一并考虑。除了大于小于号,&在拼接LIKE '%a&b%'这类语句时也必须转义成&,否则同样触发解析错误。排查这类问题时,报错信息中的行号通常指向Mapper XML文件,直接定位到具体行检查符号即可。
总结一下,两种方案没有绝对优劣:转义符写法通用稳妥,适合与动态SQL深度混用的场景;CDATA写法可读性好,但必须确保动态标签在区段之外。实际项目中建议团队统一约定其中一种风格,并在代码规范中明确记录,避免同一个项目里两种写法混杂带来的维护成本。