MyBatis XML中需用CDATA包裹含<、>的SQL,避免XML解析错误;参数应预转LocalDateTime类型;时区须统一显式转换;复杂场景推荐@SelectProvider替代XML。

MyBatis XML里写 > 和 < 会报错:Invalid content was found starting with element 'where'
这是因为 MyBatis 的 XML 解析器把 < 和 > 当成标签边界处理了,而你在 <![CDATA[...]]> 外直接写了 <= 这类符号,XML 就不合法。不是 MyBatis 问题,是 XML 本身语法限制。
常见错误写法:
<where>
create_time >= #{startTime} AND create_time <= #{endTime}
</where>看着像转义了,但实际在 XML 解析阶段就挂了——因为 > 是实体,但解析器先按标签规则扫,遇到 < 就期待一个合法起始标签,结果后面是空格或字母,直接报错。
- 必须把整段 SQL 条件包进
<![CDATA[...]]>,不能只包一部分 -
<![CDATA[...]]>必须是独立文本节点,前后不能紧贴其他字符(比如换行空格可以,但<where><![CDATA[...]]></where>中间不能有文字) - 别在 CDATA 里再手动写
或 <code>>—— 它本来就是为了让你直写<>的
日期范围查询用 <bind> 预处理参数避免类型不匹配
MySQL 的 DATETIME 字段和 Java 的 LocalDateTime 传进去时,MyBatis 默认可能当字符串拼,导致索引失效或时区错乱。尤其当你用 <![CDATA[...]]> 写死 SQL,更得确保参数类型对得上。
推荐用 <bind> 提前格式化或转换:
<bind name="startDt" value='@org.apache.commons.lang3.time.DateUtils@parseDate(startTime, "yyyy-MM-dd HH:mm:ss")'/> <bind name="endDt" value='@org.apache.commons.lang3.time.DateUtils@parseDate(endTime, "yyyy-MM-dd HH:mm:ss")'/>
- 如果项目没引入 commons-lang3,改用 MyBatis 内置的
@java.time.LocalDateTime@parse(...)(JDK8+),但注意要加static导入配置 - 更稳妥的做法是在 Java 层把
String参数转成LocalDateTime再传入,XML 里只做纯比较:<![CDATA[create_time >= #{startDt} AND create_time <= #{endDt}]]> - 别信“#{xxx} 会自动转类型”——它只管预编译占位,类型还是看 Java 对象本身
MySQL 时间范围查询踩坑:NOW()、STR_TO_DATE 和时区
有人在 CDATA 里写 NOW() - INTERVAL 7 DAY,看起来没问题,但上线后发现查不到数据——大概率是 MySQL 服务端时区和应用服务器不一致,或者字段存的是 UTC,查询却用本地时间比。
- 统一用
CONVERT_TZ()显式转换,比如:CONVERT_TZ(create_time, '+00:00', '+08:00') >= #{startTime} - 避免在 SQL 里用
STR_TO_DATE(#{dateStr}, '%Y-%m-%d'),性能差且无法走索引;Java 层转好再传 - 测试时一定要连真实库跑,别只靠 H2 或内存模式——它们对时区/日期函数支持差异大
替代方案:用 @SelectProvider 动态 SQL 更干净
当 CDATA 越来越多、逻辑越来越绕,其实说明 XML 已经在反噬可维护性。直接切到注解方式,用 Java 拼 SQL,类型安全、IDE 可跳转、单元测试也方便。
示例:
@SelectProvider(type = OrderSqlProvider.class, method = "selectByDateRange")
List<Order> selectByDateRange(@Param("start") LocalDateTime start, @Param("end") LocalDateTime end);然后在
OrderSqlProvider 里写 Java 方法返回字符串 SQL。
- 不用操心 XML 转义、CDATA 位置、缩进空格
- 可以复用 Java 工具类做日期校验、默认值填充(比如 end 为空时设为 NOW())
- 缺点是 SQL 脱离 Mapper XML,部分团队审计或 DBA 会觉得“看不见 SQL”
真正难的从来不是怎么写对一个日期条件,而是所有地方都用同一套时区理解、同一种时间精度(秒?毫秒?)、同一个 null 处理逻辑。这些细节藏在每一处 #{} 和每一段 <![CDATA[...]]> 之间,没人报错,但数据就是不对。

















