在MyBatis的XML映射文件中编写SQL时,按日期范围查询是高频需求,而SQL中的大于小于号属于XML的特殊字符,直接使用会导致XML解析错误,CDATA区就是解决这个问题的有效方案。

日期范围查询的两种常见实现方式
方式一:使用预编译参数拼接
这种方式通过#{}占位符传入参数,在XML中直接使用转义后的大于小于符号,适合参数固定的场景。
<select id="queryByDateRange" resultType="com.example.entity.Order">
SELECT * FROM t_order
WHERE create_time >= #{startTime}
AND create_time <= #{endTime}
</select>
这里的>是大于号的转义字符,<是小于号的转义字符,如果不做转义,XML解析器会把<当成标签的开始,导致解析失败。
方式二:使用CDATA区包裹特殊字符
CDATA区的作用是告诉XML解析器,区内的内容不需要解析,直接作为纯文本处理,这样就不需要手动转义大于小于号。
<select id="queryByDateRange" resultType="com.example.entity.Order">
SELECT * FROM t_order
WHERE <![CDATA[
create_time >= #{startTime}
AND create_time <= #{endTime}
]]>
</select>
可以看到在CDATA区内部,我们可以直接写>=和<=,不需要做任何转义处理,代码可读性更高。
CDATA区的使用注意事项
- CDATA区的语法是
<![CDATA[ 内容 ]]>,开头和结尾的标记必须完整,不能遗漏。 - CDATA区不能嵌套使用,内部不能再出现
]]>这样的字符,否则会被当成CDATA区的结束标记。 - 如果SQL中既有需要解析的动态标签(比如
<if>),又有特殊字符,不能把整个SQL都放在CDATA区里,否则动态标签不会被解析。
结合动态SQL的日期范围查询示例
实际开发中经常需要根据参数是否为空来动态拼接查询条件,这时候需要把CDATA区只用在包含特殊字符的部分。
<select id="queryByCondition" resultType="com.example.entity.Order">
SELECT * FROM t_order
<where>
<if test="startTime != null">
<![CDATA[
AND create_time >= #{startTime}
]]>
</if>
<if test="endTime != null">
<![CDATA[
AND create_time <= #{endTime}
]]>
</if>
</where>
</select>
对应的Mapper接口方法定义如下,参数使用@Param注解明确参数名称:
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
public interface OrderMapper {
List<Order> queryByCondition(
@Param("startTime") Date startTime,
@Param("endTime") Date endTime
);
}
两种转义方式的对比
| 方式 | 优点 | 缺点 |
|---|---|---|
| 手动转义特殊字符 | 可以和动态标签完全兼容,不需要额外注意嵌套问题 | 符号多的时候可读性差,容易写错转义字符 |
| 使用CDATA区 | 特殊字符不需要转义,代码可读性强 | 不能嵌套,不能包裹动态SQL标签,使用场景有限制 |
在实际开发中可以根据场景选择,如果SQL比较简单没有动态标签,优先使用CDATA区提升可读性;如果包含动态SQL标签,就使用手动转义的方式处理大于小于号。