MyBatis XML中CDATA的作用与最佳实践 1. 为什么MyBatis XML中需要CDATA在编写MyBatis映射文件时我们经常会遇到SQL语句中包含特殊字符的情况。比如下面这个查询select idfindUsers resultTypeUser SELECT * FROM users WHERE age 18 AND name LIKE %张% /select这段SQL中的和%都是XML中的特殊字符。XML解析器会将这些字符识别为标签或实体引用导致解析错误。这时候CDATA就派上用场了。CDATACharacter Data是XML中用来标记纯文本数据的特殊语法。它的核心作用是告诉XML解析器这段内容请原样处理不要解析其中的任何标记。在MyBatis中这特别适合用于包裹包含特殊字符的SQL语句。注意虽然MyBatis 3.4.6版本已经改进了对特殊字符的处理但使用CDATA仍然是保证兼容性和可读性的最佳实践。2. CDATA的基本语法与使用场景2.1 CDATA的标准写法CDATA的标准语法非常简单![CDATA[ 这里的内容会被XML解析器忽略 可以包含 等特殊字符 ]]在MyBatis中的典型应用是这样的select idfindActiveUsers resultTypeUser ![CDATA[ SELECT * FROM users WHERE status ACTIVE AND create_time #{startDate} ]] /select2.2 必须使用CDATA的几种情况根据我的项目经验以下场景必须使用CDATA包含比较运算符的SQL如,,,LIKE语句中的通配符%和_位运算符,|,^,~包含XML/HTML片段的字段值虽然少见但确实遇到过特别要注意的是即使某些版本的MyBatis能自动处理这些字符为了代码的可移植性和可读性也应该坚持使用CDATA。3. CDATA与MyBatis动态SQL的配合使用3.1 在动态SQL标签中使用CDATA动态SQL是MyBatis的强大特性当它与CDATA结合时需要特别注意语法结构。正确的做法是将CDATA放在最内层select idfindUsers resultTypeUser SELECT * FROM users where if testname ! null ![CDATA[ AND name LIKE CONCAT(%, #{name}, %) ]] /if if testminAge ! null ![CDATA[ AND age #{minAge} ]] /if /where /select3.2 常见的错误用法我在代码审查中经常看到以下错误用法CDATA包裹整个动态SQL块!-- 错误示例 -- ![CDATA[ select idfindUsers resultTypeUser SELECT * FROM users where.../where /select ]]这样会导致MyBatis的动态SQL标签失效。CDATA与${}混用的安全隐患!-- 危险示例 -- ![CDATA[ AND name LIKE %${name}% ]]虽然语法正确但使用${}容易导致SQL注入应该改用#{}配合CONCAT函数。4. CDATA的替代方案与选择建议4.1 XML实体编码的用法除了CDATA还可以使用XML实体编码来表示特殊字符字符实体编码示例WHERE age gt; #{minAge} AND age lt; #{maxAge}4.2 两种方式的对比根据我的实践经验它们的优缺点对比如下特性CDATA实体编码可读性高保持原样格式低需要转义维护性较好大段SQL清晰差需要逐个转义工具支持部分IDE高亮可能异常所有工具都支持嵌套限制不能嵌套可以多层转义适用场景大段含特殊字符的SQL少量特殊字符4.3 个人建议的选择策略简单条件单个特殊字符使用实体编码复杂SQL包含多个特殊字符时使用CDATA动态SQL优先在条件片段中使用CDATA混合情况可以组合使用如动态SQL外层不用CDATA内部条件用CDATA5. CDATA使用中的常见问题与解决方案5.1 IDE的语法高亮问题在使用IntelliJ IDEA等IDE时可能会遇到CDATA内部SQL语法高亮失效的问题。这可以通过以下方式解决安装MyBatis插件如Free MyBatis Plugin在设置中启用识别CDATA内的SQL或者使用注释辅助高亮![CDATA[ /* 这里写SQL */ SELECT * FROM table ]]5.2 代码格式化导致的换行问题XML格式化工具可能会在CDATA内部添加不必要的换行。我的建议是在IDE中配置XML格式化规则保留CDATA原样或者在CDATA内部自己控制格式![CDATA[SELECT * FROM users WHERE id #{id}]]5.3 与注释的配合使用CDATA内部不应该包含XML注释否则会导致解析错误。正确的注释方式是select idfindUser resultTypeUser ![CDATA[ -- SQL注释使用SQL风格的注释 SELECT * FROM users /* 也可以使用这种注释 */ WHERE id #{id} ]] /select6. 性能与安全考量6.1 CDATA对解析性能的影响有些人担心CDATA会增加XML解析开销。实际上现代XML解析器对CDATA的处理已经高度优化MyBatis启动时解析映射文件的开销可以忽略运行时根本不涉及CDATA解析我曾经用JMeter测试过使用CDATA和不使用CDATA的查询性能差异在0.1%以内。6.2 安全最佳实践虽然CDATA本身不会引入安全问题但要注意绝对不要在CDATA中直接拼接用户输入即使使用CDATA参数也应该用#{}而非${}对于LIKE语句应该这样写![CDATA[ AND name LIKE CONCAT(%, #{keyword}, %) ]]而不是![CDATA[ AND name LIKE %${keyword}% ]]7. 实际项目中的经验分享7.1 复杂查询的格式化技巧对于复杂的多表查询我习惯这样组织CDATAselect idfindUserDetails resultMapuserDetailMap ![CDATA[ SELECT u.id, u.name, d.department_name, p.phone_number FROM users u JOIN departments d ON u.dept_id d.id LEFT JOIN phones p ON u.id p.user_id WHERE u.status ACTIVE AND u.create_time #{startDate} ]] if testdeptId ! null ![CDATA[ AND u.dept_id #{deptId} ]] /if /select这种格式既保持了可读性又确保了特殊字符的安全。7.2 与MyBatis-Plus的兼容性在使用MyBatis-Plus时CDATA的用法与原生MyBatis完全一致。但要注意在Wrapper条件中不需要CDATA因为那是Java代码只有XML中直接写的SQL需要CDATA例如自定义SQL片段sql idsafeCondition ![CDATA[ AND age #{age} ]] /sql7.3 调试技巧当CDATA导致问题时可以先去掉CDATA看是否是它引起的问题检查CDATA是否完整闭合使用XML验证工具检查文件有效性查看MyBatis启动日志中的SQL解析情况我在实际项目中遇到过因为CDATA未闭合导致整个映射文件失效的情况错误日志会提示XML解析错误但不会直接指出是CDATA的问题。