MyBatis查询数据库(3)

 前言🍭

❤️❤️❤️SSM专栏更新中,各位大佬觉得写得不错,支持一下,感谢了!❤️❤️❤️

Spring + Spring MVC + MyBatis_冷兮雪的博客-CSDN博客

前面我们讲解了MyBatis增删改查基本操作,下面我们来深入了解MyBatis其中不同和需要注意的地方。

一、查询操作🍭

1、单表查询🍉

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

UserController 实现代码如下:

//url 路径名直接全部小写即可
    @RequestMapping("/getuserbyid")
    public Userinfo geUserById(Integer id){
        if (id==null)
            return null;
        return userService.getUserById(id);
    }

 UserMapper 实现代码如下:

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

UserMapper.xml 实现代码如下:

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

Ⅰ、参数占位符 #{} 和 ${}🍓

  • #{}:预编译处理

  • ${}:字符直接替换

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

上面代码我们使用的是${},去传递Integer(整数)类型的参数时,是没有问题的,但如果传递的是String类型的话,程序就会报错。

下面我们通过 根据用户名查询用户(getUserByName)来看看

 这就直接报错了,说是没有admin这个用户,这是因为${}是直接替换值(不会管你是什么类型,都直接替换),而SQL语句中字符串需要使用单引号,这就会查询不到,报错。

正确SQL: 

两者区别总结:

1、#{}:安全参数占位符

  • #{}是MyBatis的预编译语句中的参数占位符,用于传递参数值。它会自动进行参数值的类型转换和防止SQL注入攻击。
  • 在使用#{}时,MyBatis会将参数值通过JDBC的PreparedStatement接口进行预编译,参数值会被当做字符串类型处理,然后由JDBC驱动来负责将其转换成对应的数据库类型,这样可以避免SQL注入问题。
  • 例子:SELECT * FROM users WHERE id = #{userId}

2、${}:字符串替换占位符

  • ${}是字符串替换占位符,用于直接将参数的值替换到SQL语句中。在使用${}时,参数值会被直接替换进SQL语句中,不会进行预编译或类型转换。
  • 由于${}直接替换参数值到SQL语句中,可能存在SQL注入的风险,因此不建议在动态SQL中使用${}来传递用户输入的参数。
  • 例子:SELECT * FROM users WHERE id = ${userId}

那这为什么还有${}去传递参数呢?全部使用#{}不是更好?

Ⅱ、${}优点🍓

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

UserMapper接口:

//根据id查询用户 并且进行排序
    List<Userinfo> getAllByOrder(@Param("order") String order);

UserMapper.xml:

<select id="getAllByOrder" resultType="com.example.ssmdemo1.entity.Userinfo">
        select * from userinfo order by id ${order}
</select>
单元测试:
@Test
    void getAllByOrder() {
        List<Userinfo> list = userMapper.getAllByOrder("asc");
        System.out.println(list);

    }

单元测试成功:

Ⅲ、SQL 注入问题 🍓

UserMapper接口:

Userinfo login(@Param("username")String username,@Param("password")String password);

UserMapper.xml:

<select id="login" resultType="com.example.ssmdemo1.entity.Userinfo">
        select *from userinfo where usernaem='${username}' and password='${password}'
</select>

因为${}是直接引用,所以我们加上了单引号。 这样就和使用#{}是一样的了

单元测试:

@Test
    void login() {
        String username="2";
        String password="2";
        Userinfo userinfo=userMapper.login(username,password);
        System.out.println("用户登录"+(userinfo==null?"失败":"成功"));
    }

 可以看到此时用户是登录成功的: 

但是这样写有SQL注入的风险,我们修改代码如下,然后运行代码

@Test
    void login() {
        String username="2";
        String password="'or 1 ='1";
        Userinfo userinfo=userMapper.login(username,password);
        System.out.println("用户登录"+(userinfo==null?"失败":"成功"));
    }

单元测试:

可以看到上面单元测试失败了,但仔细看,是因为返回了5个Userinfo对象,但我只需要接收一个

所以报错了,如果接受的是List<Userinfo>就不会报错了

UserMapper接口:

List<Userinfo> login(@Param("username")String username, @Param("password")String password);

单元测试:

@Test
    void login() {
        String username="2";
        String password="' or 1='1";
        List<Userinfo> userinfo=userMapper.login(username,password);
        System.out.println("用户登录"+(userinfo==null?"失败":"成功"));
    }

单元测试成功:

可以看到这是非常可怕的,居然把我所有用户信息返回了(数据库中一共有五个用户),也就是说,你想使用哪个用户登录就可以使用哪个用户登录。

如果使用#{},可能存在这个问题吗?

<select id="login" resultType="com.example.ssmdemo1.entity.Userinfo">
        select *from userinfo where username=#{username} and password=#{password}
</select>

 单元测试失败:

 结论:用于查询的字段,尽量使用 #{} 预查询的方式,而需要传递关键字时,使用${}

Ⅳ、like查询🍓

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

UserMapper接口:

List<Userinfo> getListByName(@Param("username")String username);

UserMapper.xml:

<select id="getListByName" resultType="com.example.ssmdemo1.entity.Userinfo">
        select * from userinfo where username like '%#{username}%'
</select>

单元测试:

@Test
    void getListByName() {
        String username="n";
        List<Userinfo> list=userMapper.getListByName(username);
        System.out.println("list:"+list);
    }

 运行报错:

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

select * from userinfo where username like '%'n'%'

我们替换${}试试:

但是前面说了使用${}有SQL注入的风险,所有这是不能直接使用 ${},可以考虑使用 mysql 的内置函数 concat() 来处理,实现代码如下: 

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

单元测试成功: 

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

2、多表查询🍉

如果是增、删、改返回搜影响的行数,那么在 UserMapper.xml 中是可以不设置返回的类型的,如:

    <insert id="add">
        insert into userinfo(username,password) values(#{username},#{password})
    </insert>
    <delete id="delById">
        delete from userinfo where id=#{id}
    </delete>
    <update id="upUserName">
        update userinfo set username=#{username} where id=#{id}
    </update>

然而即使是最简单查询用户的名称也要设置返回的类型,否则会出现如下错误:

查询不设置返回类型的错误示例演示:

controller 代码:

@RequestMapping("/getname")
public String getNameById(Integer id) {
     return userService.getNameById(id);
}
UserMapper.xml 实现代码:
<select id="getNameById">
        select username from userinfo where id=#{id}
</select>

访问接口执行结果如下:

显示运行了⼀个查询但没有找到结果映射,也就是说对于 <select> 查询标签来说至少需要两个属性:

  • id 属性:用于标识实现接口中的那个方法;

  • 结果映射属性:结果映射有两种实现标签:<resultMap> 和 <resultType>。

Ⅰ、返回类型:resultType  🍓

绝大数查询场景可以使⽤ resultType 进行返回,如下代码所示:

<select id="getNameById" resultType="java.lang.String">
         select username from userinfo where id=#{id}
</select>
<select id="getUserByName" resultType="com.example.ssmdemo1.entity.Userinfo">
        select * from userinfo where username=#{username}
</select>

它的优点是使用方便,直接定义到某个实体类即可。

Ⅱ、返回字典映射:resultMap 🍓

resultMap使用场景:
  • 字段名称和程序中的属性名不同的情况,可使用 resultMap 配置映射;

  • ⼀对⼀和⼀对多关系可以使用 resultMap 映射并查询数据。

当程序中的属性值与数据库中的字段名不一样时🍒

@Data
public class Userinfo {
    private Integer id;
    private String name;//数据库名为:username
    private String password;
    private String photo;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
    private Integer state;
}

 我们去单元测试,进行用户的查询:

@Test
    void getUserByName() {
        Userinfo userinfo=userMapper.getUserByName("admin");
        System.out.println(userinfo);
    }

 打印出来就发现,name没有被赋值,为null,这就是因为字段名与属性值不一样的结果。

这个时候就可以使用 resultMap 了,resultMap 的使用如下:

 UserMapper.xml:

<resultMap id="baseMap" type="com.example.demo.entity.Userinfo">
        <id column="id" property="id"></id>
        <result column="username" property="name"></result>
        <result column="password" property="password"></result>
        <result column="photo" property="photo"></result>
        <result column="createtime" property="createtime"></result>
        <result column="updatetime" property="updatetime"></result>
        <result column="state" property="state"></result>
</resultMap>
<select id="getUserByName" resultMap="baseMap">
        select * from userinfo where username=#{username}
</select>

这样查询的结果就有值了,如下图所示:

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

如果你一定需要使用resultType,也是可以实现的:

<select id="getUserByName" resultType="com.example.ssmdemo1.entity.Userinfo">
        select id,username as name,password,photo,createtime,updatetime from userinfo where username=#{username}
    </select>

Ⅲ、多表查询🍓

在多表查询时,如果使用 resultType 标签,在⼀个类中包含了另⼀个对象是查询不出来被包含的对象的,比如以下实体类:

@Data
public class ArticleInfo {
 private Integer id;
 private String title;
 private String content;
 private LocalDateTime createtime;
 private LocalDateTime updatetime;
 private Integer rcount;
 // 包含了 userinfo 对象
 private UserInfo user;
}

程序的执行结果如下图所示:

此时我们就需要使用特殊的手段来实现联表查询了。

通过VO对象🍒

ArticleInfo

package com.example.ssmdemo1.entity;

import lombok.Data;

import java.time.LocalDateTime;

@Data
public class ArticleInfo {
    private Integer id;
    private String title;
    private String content;
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    private int uid;
    private Integer rcount;
    private Integer state;
}

VO对象

package com.example.ssmdemo1.entity.vo;

import com.example.ssmdemo1.entity.ArticleInfo;

public class ArticleInfoVO extends ArticleInfo {
    private String username;

    @Override
    public String toString() {
        return "ArticleinfoVO{" +
                "username='" + username + '\'' +
                "} " + super.toString();
    }
}

 ArticleMapper.xml

进行多表程序:

<?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.ssmdemo1.mapper.ArticleMapper">
    <select id="getById" resultType="com.example.ssmdemo1.entity.vo.ArticleInfoVO">
        select a.*,u.username from articleinfo a
        left join userinfo u on u.id=a.uid
        where a.id=#{id}
    </select>
</mapper>

单元测试:

package com.example.ssmdemo1.mapper;

import com.example.ssmdemo1.entity.vo.ArticleInfoVO;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class ArticleMapperTest {
    @Autowired
    private ArticleMapper articleMapper;
    @Test
    void getById() {
        ArticleInfoVO articleInfoVO=articleMapper.getById(5);
        System.out.println(articleInfoVO);
    }
}

单元测试成功,查询到了用户id为5的 文章表的文章信息 和 用户表中的用户名:

大部分时候多表联查解决方案:

 联表查询语句(left join/inner)+xxxVO(新建的实体类)就可以解决

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/55969.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Mac上命令

1. block端口&#xff1a; sudo cp /etc/pf.conf /etc/pf443.conf 编辑pf443.conf&#xff0c;vim /etc/pf443.conf&#xff0c;如 block on en0 proto udp from any to any port 9000 # block UDP port 9000 block on en0 proto tcp from any to any port 5004 # bloc…

【Spring AOP + 自定义注解 + 动态数据源 实现主从库切换读写分离】—— 案例实战

&#x1f4a7; S p r i n g A O P 主从数据源切换 读写分离 自定义注解案例实战&#xff01; \color{#FF1493}{Spring AOP 主从数据源切换 读写分离 自定义注解 案例实战&#xff01;} SpringAOP主从数据源切换读写分离自定义注解案例实战&#xff01;&#x1f4a7; …

Docker Sybase修改中文编码

镜像&#xff1a;datagrip/sybase 镜像默认用户名sa&#xff0c;密码myPassword&#xff0c;服务名MYSYBASE 1.进入容器 docker exec -it <container_name> /bin/bash2.加载Sybase环境变量 source /opt/sybase/SYBASE.sh3.查看是否安装了中文字符集 isql -Usa -PmyP…

【Unity学习笔记】对象池

文章目录 设计思路总体设计从生命周期考虑 一些代码 对象池这个东西老生常谈了&#xff0c;使用它的好处在于&#xff1a;当我们需要重复创建或者销毁一些物体&#xff0c;例如限制子弹数量上限为10发&#xff0c;当射出第11发就需要使第10发消失&#xff0c;第11出现。销毁10号…

NullPointerException导致手机重启案例分析

和你一起终身学习&#xff0c;这里是程序员Android 经典好文推荐&#xff0c;通过阅读本文&#xff0c;您将收获以下知识点: 一、 Framework 层对象 空指针导致手机重启。二、解决方案&#xff0c;规避空指针三、Telecom APK 控制导致的重启举例 一、 Framework 层对象 空指针导…

XGBoost的基础思想与实现

目录 1. XGBoost VS 梯度提升树 1.1 XGBoost实现精确性与复杂度之间的平衡 1.2 XGBoost极大程度地降低模型复杂度、提升模型运行效率 1.3 保留了部分与梯度提升树类似的属性 2. XGBoost的sklearnAPI实现 2.1 sklearn API 实现回归 2.2 sklearn API 实现分类 3. XGBoost回…

MySQL 极速安装使用与卸载

目录 mysql-5.6.51 极速安装使用与卸载 sqlyog工具 mysql简化 mysql-8.1.0下载配置 再完善 mysql-5.6.51 极速安装使用与卸载 mysql-8.1.0下载安装在后 mysql中国官网 MySQLhttps://www.mysql.com/cn/ 点击MySQL社区服务器 点击历史档案 下载完 解压 用管理员运行cmd&a…

ODIN_1靶机详解

ODIN_1靶机复盘 下载地址&#xff1a;https: //download.vulnhub.com/odin/odin.ova 靶场很简单&#xff0c;一会儿就打完了。 靶场说明里提醒说加一个dns解析。 我们在/etc/hosts加一条解析 就能正常打开网站了&#xff0c;要么网站打开css是乱的。 这里看到结尾就猜测肯定…

uniapp自定义消息语音

需求是后端推送的消息APP要响自定义语音&#xff0c;利用官方插件&#xff0c;总结下整体流程 uniapp后台配置 因为2.0只支持uniapp自己的后台发送消息&#xff0c;所以要自己的后台发送消息只能用1.0 插件地址和代码 插件地址: link let isIos (plus.os.name "iOS&qu…

<C++> 三、内存管理

1.C/C内存分布 我们先来看下面的一段代码和相关问题 int globalVar 1; static int staticGlobalVar 1; void Test() {static int staticVar 1;int localVar 1;int num1[10] {1, 2, 3, 4};char char2[] "abcd";const char *pChar3 "abcd";int *ptr1…

小乌龟(TortoiseGit)连接GitLab

目录 &#x1f35f;写在前面 &#x1f35f;实验目标 &#x1f35f;安装gitlab &#x1f37f;1、安装依赖 &#x1f37f;2、下载清华gitlab包 &#x1f37f;3、安装gitlab &#x1f37f;4、修改配置文件 &#x1f37f;5、管理命令 &#x1f35f;访问gitlab &#x1f35f;界面设置…

《吐血整理》进阶系列教程-拿捏Fiddler抓包教程(16)-Fiddler如何充当第三者再识AutoResponder标签-上

1.简介 Fiddler充当第三者&#xff0c;主要是通过AutoResponder标签在客户端和服务端之间&#xff0c;Fiddler抓包&#xff0c;然后改包&#xff0c;最后发送。AutoResponder这个功能可以算的上是Fiddler最实用的功能&#xff0c;可以让我们修改服务器端返回的数据&#xff0c…

html学习3(表格table、列表list)

1、html表格由<table>标签来定义。 <thead>用来定义表格的标题部分&#xff0c;其内部用 <th > 元素定义列的标题&#xff0c;可以使其在表格中以粗体显示&#xff0c;与普通单元格区分开来。<tbody>用来定义表格的主体部分&#xff0c;其内部用<t…

力扣 63. 不同路径 II

题目来源&#xff1a;https://leetcode.cn/problems/unique-paths-ii/description/ C题解&#xff1a;动态规划五部曲。 确定dp数组&#xff08;dp table&#xff09;以及下标的含义。dp[i][j] &#xff1a;表示从(0, 0)出发&#xff0c;到(i, j) 有dp[i][j]条不同的路径。确定…

Vue 2.x 项目升级到 Vue 3详细指南【总结版】

文章目录 0.前言1.升级教程1.1. 升级 Vue CLI&#xff1a;1.2. 安装 Vue 3&#xff1a;1.3. 更新 Vue 组件&#xff1a;1.4. 迁移全局 API&#xff1a;1.5. 迁移路由和状态管理器&#xff1a;1.6. 迁移 TypeScript&#xff1a;1.7. 迁移测试代码&#xff1a; 2.迁移总结2.0. 这…

ESP32cam系列教程003:ESP32cam实现远程 HTTP_OTA 自动升级

文章目录 1.什么是 OTA2. ESP32cam HTTP_OTA 本地准备2.1 HTTP OTA 升级原理2.2 开发板本地基准程序&#xff08;程序版本&#xff1a;1_0_0&#xff09;2.3 开发板升级程序&#xff08;程序版本&#xff1a;1_0_1&#xff09;2.4 本地 HTTP_OTA 升级测试2.4.1 本地运行一个 HT…

使用Linux部署Jpress博客系统

环境要求 linux系统&#xff1a;我使用的操作系统是CentOS7 数据库&#xff1a;mysql&#xff0c;也可以使用mariadb jdk&#xff1a;与你的Linux操作系统能兼容的版本 tomcat&#xff1a;我使用的是tomcat8版本 如果没有数据库&#xff0c;请先自行下载 如果没有安装jdk…

Agile manifesto principle (敏捷宣言的原则)

Agile在管理中越来越受推崇&#xff0c;最初是由于传统的软件开发管理方式&#xff08;瀑布模型&#xff09;面对日益复杂的需求&#xff0c;无法Delivery令人满意的结果&#xff0c;经过总结探索&#xff0c;2001年&#xff0c;由行业代表在一次聚会中提出Agile敏捷mainfesto&…

RK3588开发板 (armsom-w3) 之 USB摄像头图像预览

硬件准备 RK3588开发板&#xff08;armsom-w3&#xff09;、USB摄像头&#xff08;罗技高清网络摄像机 C93&#xff09;、1000M光纤 、 串口调试工具 v4l2采集画面 v4l2-ctl是一个用于Linux系统的命令行实用程序&#xff0c;用于控制视频4 Linux 2&#xff08;V4L2&#xff0…

P1257 平面上的最接近点对

题目 思路 详见加强加强版 代码 #include<bits/stdc.h> using namespace std; #define int long long const int maxn4e510; pair<int,int> a[maxn]; int n; double d1e16; pair<int,int> vl[maxn],vr[maxn]; void read() { cin>>n;for(int i1;i<…