11. Mybatis 的增删查改【万字详解】

news2024/9/29 1:30:38

目录

1. 数据的查找 select

1.1 查询所有数据

1.2 通过 id 进行查找

2. 插入数据 insert

3. 修改数据 update

4. 删除数据 delete

5. $ 和 # 的区别

5.1 SQL 注入 用户登录

6. Spring Boot 打印 SQL 日志

7. order by 排序

8. like 查询

9. 通过页面返回数据

10. 总结


在上篇文章中我们介绍了 mybatis 的相关概念,通过 mybatis 对数据库中的数据进行查找,接下来我们将具体的介绍 Mybatis 的增删查改。

同时本篇文章还详细介绍了 $ 和 # 的区别(重点)。

1. 数据的查找 select

1.1 查询所有数据

通过 workbench,我们可以看到数据库中的现有数据:

 接下来我们在 UserMapper.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.demo.mapper.UserMapper">
    <select id="queryAll" resultType="com.example.demo.model.User">
        select * from userinfo
    </select>
</mapper>

接下来在 UserMapper 类中添加查找方法: 

@Mapper
public interface UserMapper {
    List<User> queryAll();
}

在上述类的方法中右键,选择生成,选择测试,即可生成测试类。接下来,在 UserMapperTest 类中进行自测: 

@Slf4j
@SpringBootTest
class UserMapperTest {
    @Autowired
    private UserMapper userMapper;
    @Test
    void queryAll() {
        List<User> users = userMapper.queryAll();
        log.info(users.toString());
    }
}

运行结果如下图所示,可以看到查找到了数据库中现有的所有数据: 

1.2 通过 id 进行查找

首先,我们在 UserMapper.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.demo.mapper.UserMapper">
    <select id="queryAll" resultType="com.example.demo.model.User">
        select * from userinfo
    </select>
    <select id="queryById" resultType="com.example.demo.model.User">
        select * from userinfo where id = ?
    </select>
</mapper>

接下来在 UserMapper 类中添加查找方法: 

@Mapper
public interface UserMapper {
    // 查询所有数据
    List<User> queryAll();
    // 通过 id 查询数据
    User queryById(@Param("uid") Integer id);
}

 在上述类的方法中右键,选择生成,选择测试,生成测试类:

 可以看到生成的测试类中包含以下方法:

接下来,我们编写测试的代码:

@Slf4j
@SpringBootTest
class UserMapperTest {
    @Autowired
    private UserMapper userMapper;
    @Test
    void queryAll() {
        List<User> users = userMapper.queryAll();
        log.info(users.toString());
    }

    @BeforeEach
    void setUp() {
        log.info("before...");
    }

    @AfterEach
    void tearDown() {
        log.info("after...");
    }

    @Test
    void queryById() {
        log.info("queryById...");
        User user = userMapper.queryById(1);
        log.info(user.toString());
    }
}

运行结果如下: 

需要注意的是,当我们只有一个参数时,可以不写注解;但是写了注解时,SQL 语句需要和注解中使用的参数保持一致

当只有一个参数时,不仅可以不写参数,参数的名称也可以不同,以下两条语句均可以成功执行:

User queryById(Integer id);
User queryById(Integer aaa);

2. 插入数据 insert

我们先来看一下数据库中都有哪些数据:

可以看到 id 是自增的,createtime 和 updatetime 都是当前时间,因此我们在插入数据时不用添加。

接下来我们在 UserMapper.xml 文件中添加以下内容:

<insert id="insert">
        insert into userinfo (username,password,photo)values(#{username},#{password},#{photo})
</insert>

在 UserMapper 类中添加插入方法: 

@Mapper
public interface UserMapper {
    // 查询所有数据
    List<User> queryAll();
    // 通过 id 查询数据
    User queryById(@Param("uid") Integer id);
    // 插入数据
    Integer insert(User user);
}

在上述类的方法中右键,选择生成,选择测试,即可生成测试类,编写测试方法:

@Slf4j
@SpringBootTest
class UserMapperTest {
    @Autowired
    private UserMapper userMapper;
    @Test
    void queryAll() {
        List<User> users = userMapper.queryAll();
        log.info(users.toString());
    }

    @BeforeEach
    void setUp() {
        log.info("before...");
    }

    @AfterEach
    void tearDown() {
        log.info("after...");
    }

    @Test
    void queryById() {
        log.info("queryById...");
        User user = userMapper.queryById(1);
        log.info(user.toString());
    }

    @Test
    void insert() {
        User user = new User();
        user.setUsername("Danny");
        user.setPassword("123456");
        user.setPhoto("123");
        Integer result = userMapper.insert(user);
        log.info("insert result:"+ result);
    }
}

接下来,在 UserMapperTest 类中进行自测: 

 在 workbench 中可以看到数据插入成功:


还可以通过第二种方法插入数据:

Integer insert2(@Param(("userinfo")) User user);
 <insert id="insert2">
        insert into userinfo (username,password,photo)values(#{userinfo.username},#{userinfo.password},#{userinfo.photo})
 </insert>

 添加测试方法:

 @Test
    void insert2() {
        User user = new User();
        user.setUsername("Danny");
        user.setPassword("123456");
        user.setPhoto("123");
        Integer result = userMapper.insert2(user);
        log.info("insert result:"+ result);
    }

可以看到,同样能够运行成功:

需要注意的是,如果使用了 @Param 注解,就必须使用 @Param 注解的命名

获取自增 id

在 UserMapper.xml 文件中添加以下内容:

<insert id="insert2" useGeneratedKeys="true" keyProperty="id">
        insert into userinfo (username,password,photo)values(#{userinfo.username},#{userinfo.password},#{userinfo.photo})
    </insert>

在 UserMapperTest 类中添加以下内容:

@Test
    void insert2() {
        User user = new User();
        user.setUsername("Danny");
        user.setPassword("123456");
        user.setPhoto("123");
        Integer result = userMapper.insert2(user);
        log.info("insert result:"+ result + ", 自增id:" + user.getId());
    }

此时可以看到自增 id 为:5. 

3. 修改数据 update

在修改数据库的数据之前,我们先来看一下此时数据库中的数据:

接下来我们修改 id = 3 的这行数据:

在 UserMapper.xml 文件中添加以下内容:

<update id="update">
        update userinfo set username = #{username},password = #{password} where id = #{id}
    </update>

在 UserMapper 类中添加修改方法:

@Mapper
public interface UserMapper {
    // 查询所有数据
    List<User> queryAll();
    // 通过 id 查询数据
    User queryById(@Param("uid") Integer id);
    // 插入数据
    Integer insert(User user);
    Integer insert2(@Param(("userinfo")) User user);
    // 修改数据
    void update(User user);
}

在上述类的方法中右键,选择生成,选择测试,即可生成测试类,编写测试方法:

 @Test
    void update() {
        User user = new User();
        user.setId(3);
        user.setUsername("Jenny");
        user.setPassword("root123456");
        userMapper.update(user);
    }

接下来,在 UserMapperTest 类中进行自测: 

运行成功后,可以看到数据库中 id =3 这一行的数据已经被修改了:

4. 删除数据 delete

我们先来看一下此时数据库中的数据:

接下来我们删除 id = 4 的这行数据:

在 UserMapper.xml 文件中添加以下内容:

<delete id="delete">
        delete from userinfo where id = #{id}
    </delete>

在 UserMapper 类中添加修改方法: 

@Mapper
public interface UserMapper {
    // 查询所有数据
    List<User> queryAll();
    // 通过 id 查询数据
    User queryById(@Param("uid") Integer id);
    // 插入数据
    Integer insert(User user);
    Integer insert2(@Param(("userinfo")) User user);
    // 修改数据
    void update(User user);
    // 通过 id 删除数据
    void delete(Integer id);
}

在上述类的方法中右键,选择生成,选择测试,即可生成测试类,编写测试方法:

 @Test
    void delete() {
        userMapper.delete(4);
    }

接下来,在 UserMapperTest 类中进行自测: 

运行成功后,可以看到数据库中 id =4 这一行的数据已经被删除了:

5. $ 和 # 的区别

我们将通过 id 查询的 xml 文件中的 # 改成 $:

 <select id="queryById" resultType="com.example.demo.model.User">
        select * from userinfo where id = ${uid}
    </select>

可以看到依然可以查询到数据: 

接下来我们使用 # 通过名称来查询:

User queryByName(String name);
 <select id="queryByName" resultType="com.example.demo.model.User">
        select * from userinfo where username = #{username}
    </select>
@Test
    void queryByName() {
        log.info("queryByName...");
        User user = userMapper.queryByName("Jenny");
        log.info(user.toString());
    }

可以看到成功查询到数据: 

接下来我们使用 $ 通过名称来查询:

修改 xml 文件:

<select id="queryByName" resultType="com.example.demo.model.User">
        select * from userinfo where username = ${username}
    </select>

运行代码后出现以下报错:

是因为 $ 符号传递的参数是没有加引号,直接放在参数中的

那么,如果我们自行添加引号呢?

<select id="queryByName" resultType="com.example.demo.model.User">
        select * from userinfo where username = '${username}'
    </select>

此时我们可以看到成功运行了: 

综上,我们来看一下其中的本质原因:

  • #{} :预编译处理(占位)
  • ${}:字符直接替换(字符的内容直接当作 SQL 的一部分来运行,SQL 注入影响程序的安全

 当我们执行以下 SQL 语句时,会发现数据库中的所有数据都被查出来了:

SELECT * FROM userinfo where username = '' or 1 = '1'

5.1 SQL 注入 用户登录

我们先在数据库中保留以下信息:

我们先来测试 # 正确查询:

<select id="queryByNameAndPassword" resultType="com.example.demo.model.User">
        select * from userinfo where username = #{username} and password = #{password}
    </select>
User queryByNameAndPassword(@Param("username") String username,@Param("password") String password);
 @Test
    void queryByNameAndPassword() {
        String username = "admin";
        String password = "admin";
        User user = userMapper.queryByNameAndPassword(username,password);
        log.info(user.toString());
    }

可以看到查询成功: 

再来测试 # 错误查询,直接修改测试方法:

@Test
    void queryByNameAndPassword() {
        String username = "admin";
        String password = "admin123";
        User user = userMapper.queryByNameAndPassword(username,password);
        log.info(user==null?null:user.toString());
    }

可以看到查询为空:

接下来测试 SQL 注入的情况:

@Test
    void queryByNameAndPassword() {
        String username = "admin";
        String password = "'or 1 ='1";
        User user = userMapper.queryByNameAndPassword(username,password);
        log.info(user==null?null:user.toString());
    }

可以看到使用错误的 password 显示为 null ,数据没有被查询出来:


接下来测试 $ 正确查询:

<select id="queryByNameAndPassword" resultType="com.example.demo.model.User">
        select * from userinfo where username = '${username}' and password = '${password}'
    </select>
@Test
    void queryByNameAndPassword() {
        String username = "admin";
        String password = "admin";
        User user = userMapper.queryByNameAndPassword(username,password);
        log.info(user==null?null:user.toString());
    }

可以看到成功查询到: 

接下来我们输入错误的 password :

@Test
    void queryByNameAndPassword() {
        String username = "admin";
        String password = "admin123";
        User user = userMapper.queryByNameAndPassword(username,password);
        log.info(user==null?null:user.toString());
    }

可以看到没有查询到数据,显示为 null: 

接下来我们来看 SQL 注入的情况:

@Test
    void queryByNameAndPassword() {
        String username = "admin";
        String password = "'or 1 ='1";
        User user = userMapper.queryByNameAndPassword(username,password);
        log.info(user==null?null:user.toString());
    }

 可以看到数据仍然被查询出来了:

那么 SQL 注入简单来说就是将本来不该被查询出来的数据,查询出来了。

6. Spring Boot 打印 SQL 日志

添加配置文件:

mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

可以看到直接将对应的 SQL 语句打印在了控制台:

以上就是 $ 打印的结果,是直接进行替换了。

接下来我们看一下 # 打印的结果:

所以,# 就是进行预编译,采用占位的方式。

7. order by 排序

我们先来看一下此时数据库中的数据:

接下来我们根据 id 进行降序排列:

List<User> queryByOrder(String order);
<select id="queryByOrder" resultType="com.example.demo.model.User">
        select * from userinfo order by id #{order}
    </select>
@Test
    void queryByOrder() {
        List<User> users = userMapper.queryByOrder("desc");
        log.info(users.toString());
    }

我们可以看到运行之后的结果并没有像我们想象之中的那样降序排列,而是直接报错:

那么上述这种情况下,就无法使用 # 必须使用 $ 符号才可以。

所以排序时只能使用 $。

修改 xml 文件:

 <select id="queryByOrder" resultType="com.example.demo.model.User">
        select * from userinfo order by id ${order}
    </select>

此时可以看到成功进行了降序排列:

但是只要使用了 $ 就存在 SQL 注入的问题,该如何解决呢?

要从根源上解决以上问题就是不让用户输入 SQL 语句,用户只能点击,参数由后端进行拼接,后端在查询之前,对参数进行校验,只能传两个值:desc(降序) 和 asc(升序)。

8. like 查询

我们先来看一下 SQL 语句的 like 查询:

SELECT * FROM userinfo where username like "%m%";

接下来我们使用 # 来查询: 

List<User> queryByLike(String name);
<select id="queryByLike" resultType="com.example.demo.model.User">
        SELECT * FROM userinfo where username like '%#{name}%';
    </select>
@Test
    void queryByLike() {
        List<User> users = userMapper.queryByLike("m");
        log.info(users.toString());
    }

可以看到无法正确查询: 

接下来我们改成 $ 进行查询:

 <select id="queryByLike" resultType="com.example.demo.model.User">
        SELECT * FROM userinfo where username like '%${name}%';
    </select>

此时,我们可以看到查询到了数据: 

但是使用了 $ 就存在 SQL 注入的问题,因此我们需要使用 MySQL 的一个内置函数。

SELECT * FROM userinfo where username like concat('%','m','%');

可以看到使用以上 SQL 语句也可以进行查询: 

因此,我们修改 xml 文件如下:

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

可以看到使用 concat 函数后可以查询成功:

9. 通过页面返回数据

新建 UserController 类: 

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping("/selectAll")
    public List<User> selectAllUser(){
        return userService.selectAllUser();
    }
}

 新建 UserService 类:

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public List<User> selectAllUser() {
        return userMapper.queryAll();
    }
}

接下来我们运行启动类,即可在页面中看到查找的数据库信息: 

10. 总结

  1. $ 存在 SQL 注入问题,因为 # 是预编译而 $ 是字符替换;
  2. orde by 只能使用 $,通过后端控制参数的输入(只能为 desc 和 asc);
  3. # 直接用于 like 查询会报错,需要使用 MySQL 的内置函数 concat 进行字符连接

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++--菱形继承

1.什么是菱形继承 单继承&#xff1a;一个子类只有一个直接父类时称这个继承关系为单继承 多继承&#xff1a;一个子类有两个或以上直接父类时称这个继承关系为多继承 菱形继承的问题&#xff1a;菱形继承有数据冗余和二义性的问题&#xff0c;数据冗余是由于创建多个相同类型的…

【C++】优先级队列的基本概念以及其模拟实现

文章目录 补充知识&#xff1a;仿函数一、优先级队列&#xff1a;1.引入2.介绍 二、priority_queue的模拟实现1.大体框架2.私有成员函数&#xff1a;1.向下调整&#xff08;AdjustDown&#xff09;2.向上调整&#xff08;AdjustUp&#xff09; 3.公有成员函数1大小&#xff08;…

Windows驱动第一节(什么是驱动?)

本文来自微软,由本人兴趣爱好人工翻译(非机翻) What is a driver? - Windows drivers | Microsoft Learn 我想很难给驱动这个词一个准确的定义.最基础的定义是驱动是一个用于让操作系统和硬件设备通信的软件组件. 举一个例子,假设一个应用程序需要从硬件设备读取一些数据,这…

2023河南萌新联赛第(三)场:郑州大学 A - 发工资咯

2023河南萌新联赛第&#xff08;三&#xff09;场&#xff1a;郑州大学 A - 发工资咯 时间限制&#xff1a;C/C 2秒&#xff0c;其他语言4秒 空间限制&#xff1a;C/C 262144K&#xff0c;其他语言524288K 64bit IO Format: %lld 题目描述 一个公司有n个人&#xff0c;每个月都…

C++类与对象 - 3(拷贝构造函数和运算符重载)(超详细)

C类与对象 - 3 1. 拷贝构造函数1.1 概念1.2 特征 2. 赋值运算符重载2.1 运算符重载2.2 赋值运算符重载记点2.3 前置和后置重载 3. const成员函数记点 4. 取地址及const取地址操作符重载 1. 拷贝构造函数 1.1 概念 在现实生活中&#xff0c;可能存在一个与你一样的自己&#x…

RK3566 android代码编译

一、搭建环境 所用的ubuntu系统之前已编译过linux代码&#xff0c;所以只需安装编译android所需的环境。 安装jdk-8 如果之前系统没有安装则执行以下命令安装&#xff1a; sudo apt-get install openjdk-8-jdk 查看当前系统是否有jdk-8 $ sudo update-alternatives --conf…

奇舞周刊第 501 期:前端打包加个性能插件检测~ 性能不过关就发邮件告诉领导!...

记得点击文章末尾的“ 阅读原文 ”查看哟~ 下面先一起看下本期周刊 摘要 吧~ 奇舞推荐 ■ ■ ■ 前端打包加个性能插件检测~ 性能不过关就发邮件告诉领导&#xff01; 本文作者结合 Lighthouse 是一个开源的自动化工具&#xff0c;给组内的项目都在 CICD 流程上更新上了性能守卫…

每日一博 - How To Improve API Performance

文章目录 包括但局限于以下措施1. 采用分页显示2. 异步记录日志3. 利用缓存技术4. 实施负载压缩5. 管理数据库连接池 包括但局限于以下措施 1. 采用分页显示 当查询结果过多时&#xff0c;将结果分为多个页面进行显示&#xff0c;可以有效地提高系统的响应速度。这样&#xff…

高效率,38V最大输入单电感同步升/降稳压器SYV939C

SYV939是一种高压同步降压-升压转换器。该器件工作在4V至28V的宽输入电压范围内&#xff0c;具有10max平均电感电流能力。四个集成的低RDS(ON)开关最大限度地减少了传导损耗。 SYV939c包括完整的保护功能&#xff0c;如输出过流/短路保护&#xff0c;过压保护和热停机&#xff…

找到刀郎《罗刹海市》--微信小程序调用地图--【小程序花园】

微信目录集链接在此&#xff1a; 详细解析黑马微信小程序视频–【思维导图知识范围】难度★✰✰✰✰ 不会导入/打开小程序的看这里&#xff1a;参考 让别人的小程序长成自己的样子-更换window上下颜色–【浅入深出系列001】 文章目录 本系列校训学习资源的选择地图在小程序里…

数据库的下一个变革方向——云原生数据库

快速上手亚马逊云原生数据库 数据库免费试用及在线大会 亚马逊数据库产品支持免费试用&#xff0c;并且提供上手教程。【数据库免费试用&上手教程】 回看人类历史上每一次技术的跨越&#xff0c;生产力变革永远不会缺席。“云原生数据库”也已经悄然走到了第十个年头。未…

RabbitMQ 教程 | 客户端开发向导

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是 DevO…

后台管理系统中常见的三栏布局总结:使用element ui构建

vue2 使用 el-menu构建的列表布局&#xff1a; 列表可以折叠展开 <template><div class"home"><header><el-button type"primary" click"handleClick">切换</el-button></header><div class"conte…

Arcgis画等高线

目录 数据准备绘制等高线3D等高线 今天我们将学习如何在ArcGIS中绘制等高线地图。等高线地图是地理信息系统中常见的数据表现形式&#xff0c;它通过等高线将地形起伏展现得一目了然&#xff0c;不仅美观&#xff0c;还能提供重要的地形信息。 数据准备 在开始之前&#xff0c…

【javaSE】 类和对象详解

目录 面向对象的初步认知 什么是面向对象 面向对象与面向过程 类定义和使用 简单认识类 类的定义格式 注意事项 练习定义类 定义一个狗类 定义一个学生类 注意事项 类的实例化 什么是实例化 注意事项 类和对象的说明 this引用 为什么要有this引用 什么是this引…

FSM:Full Surround Monodepth from Multiple Cameras

参考代码&#xff1a;None 介绍 深度估计任务作为基础环境感知任务&#xff0c;在基础上构建的3D感知才能更加准确&#xff0c;并且泛化能力更强。单目的自监督深度估计已经有MonoDepth、ManyDepth这些经典深度估计模型了&#xff0c;而这篇文章是对多目自监督深度估计进行探…

实用科研网站(自用)

网站网址Papers With Codehttps://paperswithcode.com/AMinerhttps://www.aminer.cn/Connected Papershttps://www.connectedpapers.com/ Papers With Code Papers With Code&#xff0c;在这个网站上可以看到最新的机器学习信息&#xff0c;如&#xff1a;当前研究热点、趋势…

matplotlib从起点出发(6)_Tutorial_6_Animations

1 在matplotlib中使用动画 基于其绘图功能&#xff0c;matplotlib还提供了一个使用动画模块生成动画animation的接口。动画是一系列帧&#xff0c;其中每个帧对应于图形Figure 上的一个绘图。本教程介绍了有关如何创建此类动画的一般准则以及可用的不同选项。 import matplot…

ubuntu23.04 flush DNS caches

如何在Ubuntu 23.04中刷新DNS缓存 现在&#xff0c;如果你运行的是Ubuntu 23.04&#xff0c;"系统解决 "的方法将不再适用于你。让我们检查一下你目前的缓存大小。打开你的Ubuntu终端&#xff0c;运行以下command&#xff1a; resolvectl statistics现在&#xff0c…

Android NDK开发

工程目录图 NDK中文官网 请点击下面工程名称&#xff0c;跳转到代码的仓库页面&#xff0c;将工程 下载下来 Demo Code 里有详细的注释 代码&#xff1a;TestNDK 参考文献 Android NDK 从入门到精通&#xff08;汇总篇&#xff09;Android JNI(一)——NDK与JNI基础Android之…