MyBatis查询数据库之三(#{}vs${},like查询,resultMap,as,多表查询)

目录

查询操作

1.单表查询

[1.1 参数占位符#{}和{}](#{}和{})

[1.2 ${}的优点](#1.2 ${}的优点)

[1.3 sql注入问题](#1.3 sql注入问题)

​编辑

面试常问:${}与#{}的区别

[1.4 like查询](#1.4 like查询)

2.多表查询

[2.1 返回字典映射:resultMap](#2.1 返回字典映射:resultMap)

[2.2 多表查询](#2.2 多表查询)

[(1)建立 Articalinfo 实体类:](#(1)建立 Articalinfo 实体类:)

[(2)在 ArticleMapper 接口中写出通过用户id查询用户的方法](#(2)在 ArticleMapper 接口中写出通过用户id查询用户的方法)

(3)在ArticleMapper.xml中实现接口的方法

(4)生成测试方法


查询操作

1.单表查询

下面我们来实现⼀下根据用户 id 查询用户信息的功能

UserController 实现代码如下:

java 复制代码
    @RequestMapping("/getuserbyid")
    public Userinfo geUserById(Integer id){
        if (id==null)
            return null;
        return userService.getUserById(id);
    }

UserMapper 实现代码如下:

java 复制代码
/**
     * 根据用户id查询用户信息
     * @param id
     * @return
     */
    Userinfo getUserById(@Param("id") Integer id);

UserMapper.xml 实现代码如下:

java 复制代码
<select id="getUserById" resultType="com.example.ssmdemo1.entity.Userinfo">
        select * from userinfo where id=${id}
</select>

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

  • #{}:预编译处理
  • ${}:字符直接替换

预编译处理是指:MyBatis 在处理#{}时,会将 SQL 中的 #{} 替换为?号,使用 PreparedStatement 的 set 方法来赋值。

直接替换:是MyBatis 在预处理 ${} 时,就会把 ${} 替换成变量的值。

在正常的我们用id来查询用户时:

都是没问题的,但是当我们用username来查询时:

可以看到在我们使用 # 符时是占位符,使用 $ 符则是直接替换了,int类型的自然没有问题,但是字符串类型的就有问题了

对于MySQL是要加单引号的,而$是啥都不加,所以是错误的!!

1.2 ${}的优点

有人会说,#{}这么好使的话为什么还会有${}的存在呢?

在进行排序时(需要传递关键字时)需要使用到${},而 #{sort} 就不能实现排序查询了,因为使用 #{sort} 查询时, 如果传递的值为 String 则会加单引号,就会导致 sql 错误。

当使用#{}的时候呢?

使用$是啥都不管,直接使用原样去替换,#会加上''

$使用注意事项:一定是可以穷举的值,在使用之前一定要对传递的值进行合法性验证(安全性验证)。

1.3 sql注入问题

先看两则对比:

在这里${}完美体现了它自身的特性,但是当我们改变一下password:

这里就细思极恐了,当我们使用'${}'替换#{}的时候会这样,那不是只要我们知道别人的账号就可以登录别人的账户了吗!,我们来看看#{}会不会这样:

我们可以看到 $ 符是存在SQL注入的问题,而 # 不存在

我们使用了一个奇怪的字符时为什么会成功呢?

那 # 为什么不会这样呢?

它使用的是JDBC的预处理的方式,它就把password当成字符串即values来处理,只是值的替换,而不是SQl语句,这就是机制的不同的区别

面试常问:${}与#{}的区别

  1. $ 存在失去了注入问题,而 # 不存在
  2. $ 是直接替换,# 是预处理

1.4 like查询

在使用like查询时,使用#{}会报错,下面我们来看看是怎么回事:

这是因为使用#{}会当作字符串进行替换,就变成下面这样了

而使用 $ :

这里我们可以看到使用 $ 符是可以的,但是我们说过使用 $ 符一定要能穷举,但是这里的 '李' 是不能够穷举的 ,前面说了使用${}有SQL注入的风险,所有这是不能直接使用 ${},可以考虑使用 mysql 的内置函数 concat() 来处理,实现代码如下:

java 复制代码
<select id="findUserByName3" resultType="com.example.demo.model.User">
     select * from userinfo where username like concat('%',#{username},'%')
</select>

所以在使用like查询时应该搭配concat()函数使用。

2.多表查询

2.1 返回字典映射:resultMap

前面在xml 的 标签中写返回类型 resultType 时,直接就是定义到某个实体类就行,但这种情况只适用于字段名称和程序中属性名相同的情况下,这种就是写起来方便

但如果是字段名称和属性名不同时,继续使用 resultType 就会报错,此时就要使用 resultMap 来配置映射

在一对一、一对多关系中可以使用 resultMap 映射并查询数

使用场景:实现程序中属性和表中字段映射的功能(当程序中的属性和表中的字段不一致时,可以强行的映射到一起)

我们更改一下userinfo中的属性:

或者使用as关键字(数据库重命名)

当程序中的属性与数据库中的字段不一致时解决方案:

  1. 使用 resultMap 标签(在 mapper.xml 中定义);
  2. 使用数据库别名 as 重命名。

2.2 多表查询

(1)建立 Articalinfo 实体类:

java 复制代码
@Data
public class Articleinfo  {
    private int id;
    private String title;
    private String content;
    private String createtime;
    private String updatetime;
    private int uid;
    private int rcount;//阅读量
    private int state;
}

新增一个ArticleinfoVO实体类来帮助完成:

java 复制代码
@Data
public class ArticleinfoVO extends Articleinfo implements Serializable {
    private String username;
}

(2)在 ArticleMapper 接口中写出通过用户id查询用户的方法

java 复制代码
@Mapper
public interface ArticleMapper  {
    ArticleinfoVO getById(@Param("id") Integer id);
}

(3)在ArticleMapper.xml中实现接口的方法

java 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.ArticleMapper">

    <select id="getById" resultType="com.example.demo.eneity.vo.ArticleinfoVO">
        select a.*,u.username from articleinfo a
        left join userinfo u on u.id=a.uid
        where a.id=#{id}
    </select>
</mapper>

(4)生成测试方法

java 复制代码
@SpringBootTest
class ArticleMapperTest {

    @Autowired
    private ArticleMapper articleMapper;


    @Test
    void getById() {
        ArticleinfoVO articleinfoVO = articleMapper.getById(1);
        System.out.println(articleinfoVO);
    }
}

我们发现:

明明上面已经有结果了,但是下面的打印只有扩展字段,基类的字段去哪了?

我们来梳理一下多表查询所经历的历程:

  1. 查询
  2. 打印

我们通过打点来看看是哪一步出现了错误:

我们可以得到:查询操作是没有问题的

所以问题出在了打印这里了

我们去查看字节码后发现:

我使用的是Lombok的toString,而它只给我打印了一个拓展字段

这时候我们不需要Lombok的打印了,我们重写toString方法

在toString的时候加上父类的toString

当存在Lombok的toString方法和用户的toString方法时,打印选择的是用户的方法!!用户大于一切

最终的一个实现:联表查询语句(left join/inner join)+XXXVO 解决。

相关推荐
天天扭码5 分钟前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构
程序猿进阶5 分钟前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺10 分钟前
Spring Boot框架Starter组件整理
java·spring boot·后端
小曲程序17 分钟前
vue3 封装request请求
java·前端·typescript·vue
gma99926 分钟前
Etcd 框架
数据库·etcd
爱吃青椒不爱吃西红柿‍️29 分钟前
华为ASP与CSP是什么?
服务器·前端·数据库
陈王卜35 分钟前
django+boostrap实现发布博客权限控制
java·前端·django
小码的头发丝、35 分钟前
Spring Boot 注解
java·spring boot
午觉千万别睡过38 分钟前
RuoYI分页不准确问题解决
spring boot
java亮小白199740 分钟前
Spring循环依赖如何解决的?
java·后端·spring