MyBatis 动态 SQL 语句中出现 '<' 的问题

问题描述

映射接口方法如下:

 /**
   * 根据姓名和年龄查询用户信息
   * @param name 姓名
   * @param user 获取年龄
   * @return
   */
public List<UserEntity> selectUserByNameAndAge(@Param("name") String name, @Param("user") 		
                                               	UserEntity user);

SQL 语句映射如下:

<select id="selectUserByNameAndAge" resultMap="userResultMap">
        select * from tb_user where name = #{name} and age < #{user.age};
</select>

单元测试方法如下:

		@Test
    public void selectUserByNameAndAgeTest() {
        UserEntity user = new UserEntity();
        user.setAge(30);

        List<UserEntity> userEntities =
                userMapper.selectUserByNameAndAge("李四",user);
        System.out.println(userEntities);
        Assert.assertNotNull(userEntities);
    }

执行测试结果如下:

org.apache.ibatis.exceptions.PersistenceException: 
### Error building SqlSession.
### The error may exist in mappers/UserMapper.xml
### Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: org.apache.ibatis.builder.BuilderException: Error creating document instance.  Cause: org.xml.sax.SAXParseException; lineNumber: 29; columnNumber: 61; 元素内容必须由格式正确的字符数据或标记组成。
Process finished with exit code -1

问题分析

从报错信息显示,问题应该出在映射文件 UserMapper.xml 中,更具体说,是该文件的 29 行,这一行内容如下:

select * from tb_user where name = #{name} and age < #{user.age};

这正好是映射接口方法对应的 SQL 语句,这条语句结构还算简单的,仔细看了一下似乎没啥问题,但是为何编译时却报这条语句有问题呢?

首先,我们可以先假设编译器没有乱报错,这条 SQL 语句确实有问题,只是我们目前不知道具体问题出在哪里?

然后,我们可以将这条 SQL 语句进行简化,将内容修改如下:

select * from tb_user

可能你会有疑问,你这是干嘛呀,我这样做是想验证这条 SQL 语句到底哪里有问题。

执行测试,结果如下:

[UserEntity{id=1, userName='张三', password='123456', name='张三', age=22, sex=1, birthday=Sun Sep 02 00:00:00 IRKST 1990, created='2020-06-17 09:30:58.0', updated='2020-06-17 09:30:58.0', interests=null}, UserEntity{id=2, userName='ls', password='123456', name='李四', age=24, sex=1, birthday=Sun Sep 05 00:00:00 IRKST 1993, created='2020-06-17 09:30:58.0', updated='2020-06-17 09:30:58.0', interests=null}, UserEntity{id=5, userName='lx', password='123456', name='刘星', age=21, sex=1, birthday=Fri Jan 10 00:00:00 IRKT 1992, created='2020-06-24 17:25:23.0', updated='2020-06-24 17:25:23.0', interests=null}, UserEntity{id=6, userName='ww', password='123456', name='王五', age=21, sex=1, birthday=Fri Jan 10 00:00:00 IRKT 1992, created='2020-06-24 18:53:48.0', updated='2020-06-24 18:53:48.0', interests=null}, UserEntity{id=7, userName='zq', password='123456', name='张倩', age=21, sex=0, birthday=Mon Jul 05 00:00:00 IRKST 1993, created='2020-07-05 09:24:39.0', updated='2020-07-05 09:24:43.0', interests=null}, UserEntity{id=8, userName='ll', password='123456', name='刘丽', age=23, sex=0, birthday=Fri Jul 05 00:00:00 IRKST 1991, created='2020-07-05 09:25:44.0', updated='2020-07-05 09:25:47.0', interests=null}]

这说明简化以后的这条 SQL 语句执行没有问题,当然也不应该有什么问题,如果这么简单一条 SQL 语句都有问题,那可以推测 MyBatis 开发环境可以出了问题,那么应该所有的接口方法都会报错。

接着,我们在这条 SQL 语句基础上再添加一些条件,如下:

select * from tb_user where name = #{name} 

执行测试,结果也没问题,我这里就不打印出来了。

走到这里,相信你应该清楚了,通过前两个测试,我已经缩小了问题的范围,可以 100% 确定是 and age < #{user.age} 这里出的问题。

那究竟是哪里出了问题,我首先猜测最可能就是参数传递。为了验证我的想法是否正确,我将参数传递写死,如下:

select * from tb_user where name = #{name} and age < 30;

执行测试,结果如下:

org.apache.ibatis.exceptions.PersistenceException: 
### Error building SqlSession.
### The error may exist in mappers/UserMapper.xml
### Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: org.apache.ibatis.builder.BuilderException: Error creating document instance.  Cause: org.xml.sax.SAXParseException; lineNumber: 29; columnNumber: 57; 元素内容必须由格式正确的字符数据或标记组成。

Process finished with exit code -1

居然还报错,直接打脸,这说明猜测的参数传递错误是不对的,问题原因还是没有找到。

走到这里,估计很多初学者都快失去的耐心了,心里想这也太坑了吧,估计自己是搞不定啦。别灰心,也许我们离成功只有一步之遥。

重新整理一下思路,现在我们能够确认的是 and age < 30 这里有问题,但是确实想不出究竟问题出在哪里。

我打算再做一次尝试,再次修改这条 SQL 语句如下:

select * from tb_user where name = #{name} and age = 30;

和之前相比,无非就是把 ’<' 符号 改为 '=’ 符号而已,之所以这样改,是因为也没地方可改了,死马当成活马医。

执行测试,结果如下:

[]

居然没报错,直接打印空集合,这真是有意想不到的收获呀。

分析到现在,我们终于找到了问题的原因,原来搞了半天就是 '<' 符号惹的祸。

既然 '<' 符号有问题,那么自然会联想到 ‘>' 符号是不是也有问题,修改如下:

select * from tb_user where name = #{name} and age > 30;

执行接错,结果如下:

[]

没报错,说明 '>' 符号没有问题。

还不死心,再试一试 ‘<=' 符号,结果显示和 ’<' 符号一样报错。

现在问题已经再清晰不过了,'<' 符号和 '<=' 符号不能直接在映射文件中使用

问题解决

分析问题出在哪里,解决问题就变得比较简单了。

最简单的办法就是百度,搜索 ”MyBatis SQL 语句中出现 ’<’ 的解决方案",相信你会找到一堆解决方案,剩下的就是测试一下哪个正确罢了,我非常鼓励你动手去找,虽然有点麻烦,但是可以锻炼你的独立解决问题能力,提升自我成就感。

当然,我也会在下面提供答案,这些答案也是我在网上收集的,而且是验证 OK

  • 解决方法一

字符转义

以上这张表就是 MyBatis 映射文件特殊字符转义表,'<' 符号在 XML 映射文件中其实是特殊字符,因为它和映射文件中的标记符号 ’< / >' 无法区别,所以需要用转义字符把它替换掉。

那么,'<' 问题解决办法如下:

select * from tb_user where name = #{name} and age &lt; #{user.age};

执行测试,正确显示结果,问题搞定。

  • 解决方法二

使用

在使用 mybatis 时我们 SQL 语句是写在 XML 映射文件中,如果 SQL 语句中有一些特殊的字符的话,在解析 XML 文件的时候会被转义,但我们不希望它被转义,所以我们可以使用来解决。

是什么,这是XML语法。在CDATA内部的所有内容都会被解析器忽略,只简单当做字符串处理。

那么,'<' 问题解决办法如下:

select * from tb_user where name = #{name} and age <![CDATA[ < ]]> #{user.age};

执行测试,OK

问题总结

  • 通过以上问题分析过程,其实我已经向你展示了我在实际项目开发中常用的排错方法之一,我给它取名缩小范围法。希望初学者能够明白,授人以鱼不如授人以渔,掌握一个方法远比记住一个知识点更为重要。
  • 如果你懂一些 HTML 静态网页的知识,应该很容易想明白在映射文件中的 SQL 语句中为何 '<' 符号会报错。而且上面那张特殊字符转义表,你也会似曾相识,因为它也是 HTML 静态页面的特殊字符转义表。的作用在 HMTL 中也是相同的。说这些,我想表达的是知识之间是有关联的,明白之间的关联就可以举一反三、触类旁通。
作者:Binge
本文版权归作者和博客园共有,转载必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。
原文地址:https://www.cnblogs.com/binbingg/p/13796233.html