Mybatis多表查询与动态SQL特性详解

目录 1.较复杂的查询操作 1.1 参数占位符 #{} 和 ${} 1.2SQL注入 1.3like查询 1.4resultType与resultMap 1.4多表查询 1.4.1一对一表映射 1.4.2一对多表映射 2.动态SQL 2.1if标签 2.2trim标签 2.3where标签 2.4s

                        目录1.较复杂的查询操作1.1 参数占位符 #{} 和 ${}1.2SQL注入1.3like查询1.4resultType与resultMap1.4多表查询1.4.1一对一表映射1.4.2一对多表映射2.动态SQL2.1if标签2.2trim标签2.3where标签2.4set标签2.5foreach标签总结<p></p>

1.较复杂的查询操作

1.1 参数占位符 #{} 和 ${}

#{}:预处理符,如将id=#{2}替换为id=?,然后使用2替换?。

${}:替换符,如将id=${2}替换为id=2。

两种占位符都可以正常使用的场合:传入的参数类型是数值类型

使用${}:

select from userinfo where id=${id} select from userinfo where id=2

使用#{}:

select from userinfo where id=#{id} select from userinfo where id=?

对于这两种参数占位符,个人建议能使用#{}就使用#{},因为${}存在SQL注入的问题,以及如果传入的类型是字符串也会出类型。

只能使用#{}而不能使用${}的场合:传入参数类型为String

使用${}:

select from userinfo where username=${username} //实际执行的语句 Preparing: select from userinfo where username=张三

但是在sql中通过字符串来查询数据,是需要加上引号的,而使用${}生成的sql并没有带引号,因此不适用于字符串参数的sql。

使用#{}:

select from userinfo where username=#{username} //实际执行语句 select from userinfo where username=?

由于使用#{}是将目标参数替换为占位符,然后利用JDBC中的占位符机制实现sql语句的填充,所以使用#{}构造的sql是可以正常运行的,并且没有SQL注入的问题。

所以,#{}相比于${},它支持所有类型的参数,包括数值类与字符串类,而${}支持数值类型,不支持字符串类型的参数,但也可以在原来sql里面为${}外面加上一对引号。

select from userinfo where username='${username}' //实际执行语句 Preparing: select from userinfo where username='张三'

当传递的参数为字符串类型的时候,虽然加上一对引号,使用${}也可以做到,但是${}存在SQL注入问题,所以仍然不推荐,有关SQL注入后面我们会介绍到。

大部分场合下,使用#{}都可以解决,但还是存在一小部分只能是${}来处理的。

如当我们需要按照升序或者逆序得到数据库查询结果的时候,这种场合就只能使用${},使用#{}会报错,我们来演示一下。

首先,我们在Mapper接口中声明一个方法:作用就是按照排序获取结果集

public List getOrderList(@Param(value = "order") String order);

我们再去xml文件中去写sql语句:首先我们使用$进行演示

&lt;select   resultType="com.example.demo.model.UserInfo"&gt;
    select * from userinfo order by createtime ${order};
&lt;/select&gt;

我们进行一个单元测试,单元测试代码很简单,就是调用sql,然后得到结果集:

@Test
void getOrderList() {
    List&lt;UserInfo&gt; userMappers = userMapper.getOrderList("desc");
    System.out.println(userMappers);
}

单元测试结果:

可以正常查询,得到的结果与预期也是相同的。

我们再来试一试使用#{}来构造sql语句:

&lt;select   resultType="com.example.demo.model.UserInfo"&gt;
    select * from userinfo order by createtime #{order};
&lt;/select&gt;

单元测试逻辑与代码不变,我们再来看看单元测试执行的一个结果:

我们发现程序报错了,这是因为使用了desc的字符串替换了占位符,而我们所需要的不是一个desc字符串,而是直接一个desc的关键字,所以sql也抛出了语法错误,最终执行的sql为:

select * from userinfo order by createtime ‘desc';

期望执行的sql为:

select * from userinfo order by createtime desc;

所以在传递sql关键字的时候,不能使用#{},只能使用${}。

1.2SQL注入

SQL注入就是使用${},使用一些特殊的语句,来达到非法获取数据的目的,如不通过正确的密码获取某账户的信息,下面我们来演示一下,就以登录的例子来演示,SQL注入可以在不知道密码的前提下登录成功,并且获取到用户的相关信息。

首先我将数据库只保留一个用户信息,目的是为了方便演示SQL注入问题:

5

第一步,在Mapper接口中定义方法login,返回登录成功的用户对象。

public UserInfo login(@Param("username") String username, @Param(("password")) String password);

第二步,在xml文件中编写SQL语句,我们需要演示SQL注入,所以我们使用${}来构造sql语句。

&lt;select   resultType="com.example.demo.model.UserInfo"&gt;
    select * from userinfo where username='${username}' and password='${password}';
&lt;/select&gt;

第三步,编写测试类,我们在这个测试类中,传入有注入问题的SQL语句' or 1='1,使得不需要密码就能拿到相关的用户信息。

@Test
void login() {
    String username = "admin";
    String password = "' or 1='1";

    UserInfo userInfo = userMapper.login(username, password);
    System.out.println(userInfo);
}

单元测试运行结果:

我们在不知道用户密码的情况下,登录成功,并拿到了用户的信息。

最终执行的一段sql语句为:

select * from userinfo where username='admin' and password='' or 1='1';

相当于它在原来条件判断的语句下,后面有加上一个或的逻辑,并且或后面的表达式为true,这样就使得原来的SQL语句中的条件判断部分一定为真,所以就在密码不知道的情况下拿到了用户的基本信息。

所以我们能不使用#{}就不使用${},因为存在SQL注入问题,如果必须使用${}则需要验证一下传递的参数是否合法,比如上面定义排序的sql,传递的参数只能是desc或者是asc,如果不是就不能执行这条SQL,防止SQL注入的发生。

1.3like查询

在Mybatis中使用like查询比较特殊,因为直接使用#{}会报错,而使用${},由于输入的字符串情况很多,无法做到枚举,验证比较困难,无法避免SQL注入问题。

首先,我们来演示使用#{}进行like查询,步骤我就不详细写了,就是查询的步骤。

第一步,声明方法。

public List getListByName(@Param("username") String username);

第二步,xml写sql。

&lt;select   resultType="com.example.demo.model.UserInfo"&gt;
    select * from userinfo where username like '%#{username}%'
&lt;/select&gt;

第三步,单元测试。

@Test
void getListByName() {
    String username = "a";
    List&lt;UserInfo&gt; list = userMapper.getListByName(username);
    for (UserInfo userInfo : list) {
        System.out.println(userInfo);
    }
}

运行结果:报错了,因为#{}会被替换成一个字符串,而在这个%#{username}%语句中#{username}不能带上引号,带上就违背SQL语法,造成错误。

这个时候,由于#{}多出一对引号,${}无法枚举所有情况进行验证会产生SQL注入,所以不能直接使用#{},我们需要搭配MySQL内置的字符串拼接语句concat。

我们将sql改为concat进行字符串拼接:

&lt;select   resultType="com.example.demo.model.UserInfo"&gt;
    select * from userinfo where username like concat('%', #{username}, '%')
&lt;/select&gt;

重新测试一下,发现可以正常执行了:

1.4resultType与resultMap

resultType表示数据库返回的数据映射在java程序中所对应的类型,只要定义类中的字段名与数据库中表的字段名字一致就没有任何问题,但是如果字段名存在冲突,则冲突的字段无法获取到数据库查询的结果。

比如用户名属性在数据库中的名字是username,而在java程序类中的属性名为name,此时通过mybatis将数据传递到程序中的对象时,获取到的name属性为null,就不能正确地获取到对应的属性值,为了解决这个数据库字段与类中中字段不匹配的问题,我们需要使用到resultMap。

resultMap的使用方式就是在xml文件中设置标签,至少需要设置两个属性,一个是id表示你这个resultMap标签的名字,还有一个是type属性,它表示映射到程序中类的类型,需包含包名。

这个标签里面需要设置至少两个子标签,一个是id标签,另外一个是result标签,前者表示主键,后者表示数据库表中普通的列,这两种标签也是至少需要设置两个属性,一个是column表示数据库表中的字段名,另外一个是property表示程序类中的字段名,如果只是在单表进行查询,只设置不同字段名的映射就可以了,但是如果是多表查询,必须将数据表中所有的字段与类中所有的字段生成映射关系。

就像下面这样,图中类与数据表字段是相同的,实际情况会存在不同的字段名:

1.4多表查询

27

方法声明:

//使用动态sql批量删除元素
public int deleteIds(List&lt;Integer&gt; ids);

动态SQL语句:

&lt;delete id="deleteIds"&gt;`在这里插入代码片`
    delete from userinfo where id in
    &lt;foreach collection="ids" open="(" close=")" separator="," item="id"&gt;
        #{id}
    &lt;/foreach&gt;
&lt;/delete&gt;

单元测试代码:

删除数据库中id为10 11 12的用户。

@Test
void deleteIds() {
    List&lt;Integer&gt; ids = new ArrayList&lt;&gt;();
    ids.add(10);
    ids.add(11);
    ids.add(12);

    int res = userMapper.deleteIds(ids);
    log.info("受影响的行数" + res);
}

运行结果:

成功生成了批量删除的SQL,这就是foreach标签的作用,它能够遍历集合。

19

总结

到此这篇关于Mybatis多表查询与动态SQL特性的文章就介绍到这了,更多相关Mybatis多表查询与动态SQL内容请搜索每日运维以前的文章或继续浏览下面的相关文章希望大家以后多多支持每日运维!

                        【文章转自 东台网站开发 http://www.1234xp.com/dongtai.html 欢迎留下您的宝贵建议】