通过MyBatis(下)

news2024/9/27 7:23:27

作者:~小明学编程 

文章专栏:spring框架

格言:热爱编程的,终将被编程所厚爱。
在这里插入图片描述

目录

对MyBatis进行单元测试

springboot的单元测试

生成单元测试类

MyBatis中的增删查改

${} 和 #{} 的区别

SQL注入

模糊查询中的问题

resultMap 和 resultType

多表查询

一对一

一对多

动态SQL

if标签

trim标签

where标签

set标签

foreach标签


对MyBatis进行单元测试

springboot的单元测试

前面我们说到了如何进行mybatis的操作,但是mybatis的操作依旧非常的繁琐想要测试我们的一块代码有没有问题的话是非常的麻烦的,所以下面我们需要介绍一下在springboot中进行单元测试。

单元测试:

  1. 单元测试是对程序中的 最小单元 进行检查和验证的过程就叫做单元测试。
  2. 开发者通过一小段代码,来检验代码的一个很小的功能是否正确,是否完善。

单元测试的优点:

  1. 单元测试不需要启动 Tomcat。
  2. 如果中途改动了代码,在项目打包的时候会发现错误,因为打包之前,所有单元测试都必须通过,然后才能打包成功。
  3. 如果不使用单元测试的话,会导致访问本地数据库,也就是会 “污染” 本地数据库。

生成单元测试类

 

//表示当前的单元测试运行在spring boot项目中
@SpringBootTest
class UserMapperTest {
    void getUserById() {

    }
}

 如此一来就生成了我们的单元测试的代码,想要测试的话需要添加一些代码。

@Resource
    private UserMapper userMapper;
    @Test
    void getUserById() {
        UserInfo userInfo = userMapper.getUserById(1);
        System.out.println(userInfo);
        Assertions.assertNotNull(userInfo);//判断当前的返回的对象是否为空
    }

 

MyBatis中的增删查改

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

test文件:

    @Test
    @Transactional
    void add() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("张三");
        userInfo.setPassword("1234");
        userInfo.setPhoto("000");
        int num = userMapper.add(userInfo);//影响的行数
        Assertions.assertEquals(num,1);

    }

这里使用了@Transactional的注解,这个注解主要的目的就是为了防止我们的sql污染了我们的数据库,如此操作我们对数据库的改变就不会提交也就影响不到我们的数据库了。

    <delete id="del">
        delete from userinfo where id=#{id}
    </delete>
    @Transactional
    @Test
    void del() {
        int n = userMapper.del(1);
        Assertions.assertEquals(n,1);
    }

    <select id="getUserById" resultType="com.example.demo.model.UserInfo">
        select * from userinfo where id=#{id}
    </select>
    @Test
    void getUserById() {
        UserInfo userInfo = userMapper.getUserById(1);
        System.out.println(userInfo);
        Assertions.assertNotNull(userInfo);
    }

    <update id="update">
        update userinfo set username=#{username} where id=#{id}
    </update>
    @Test
    void getUserById() {
        UserInfo userInfo = userMapper.getUserById(1);
        System.out.println(userInfo);
        Assertions.assertNotNull(userInfo);
    }

 可以看到我们的测试全部通过了。

${} 和 #{} 的区别

我们在xml文件中编写sql语句的时候可以使用$或者#来进行占位那么这两者有什么区别呢?

 可以看到我们用#的时候在执行sql的时候其实是有一个预处理的。

 但是我们使用$的时候只是做了一个简单的替换,这就会导致一个问题就是当我们替换的是一个字符串的时候会因为没有双引号而报错。既然这样那么我们总是使用#{}不就一劳永逸了吗,当然不是的,下面这种情况就必须使用${}。

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

这段代码是我们根据创建时间来进行一个排序的,这里就需要传入我们的关键字desc或者asc,如果我们再去使用#{}的话相当于传入了一个带双引号的字符串这显而易见是会报错的。

  • #{} 是预处理,${} 是直接替换。
  • #{} 适用于所有类型的参数匹配,${} 指适用于数值类型。
  • #{} 性能高,并且没有安全问题。但 ${} 存在 SQL 注入的问题。
  • 如果在 构造SQL语句 的时候,如果替换的参数,是 SQL 关键字,使用 ${} 更好,比如说排序的时候,直接用 desc,asc 这样去替换。如果是用于字段 的话,需要获取到参数类型的信息,使用 #{} 更好。

SQL注入

所谓的sql注入就是我们在输入数据的时候将我们的数据当作sql语句来执行,然后这样就会导致我们的底层sql在执行的时候会存在误差,下面就来举一个例子来演示一下我们的sql注入问题。

    <select id="login" resultType="com.example.demo.model.UserInfo">
        select * from userinfo where username='${username}' and password='${password}'
    </select>

以上是一个简单的根据我们的用户名和密码来查找我们的数据的sql语句。

    @Test
    void login() {
        String username = "a";
        String password = "' or 1='1";
        List<UserInfo> userInfo = userMapper.login(username,password);
        log.info("用户:"+userInfo);
    }

这里我们将密码改成这种奇怪的字符串,接着我们来看一下查询的结果。


这里我们可以看到,最终的查询语句变成了这样,直接将我们的所有数据都给查到了,这种情况显然不是我们想要看到的而且这种情况也是非常的危险的。

接着我们换成#再看看:

 这个时候可以发现我们此时的password只是一个混乱的字符串,根本就查询不到任何的数据。

模糊查询中的问题

下面我们想要根据用户名中的一个字符来查询数据,这个时候我们应该怎么来写呢?

    <select id="getUserByName" resultType="com.example.demo.model.UserInfo">
        select * from userinfo where username like "%${username}%"
    </select>

以上是我们用$符来做的一个sql查询,但是这种写法肯定会有sql注入的问题,这个时候就需要我们#符来处理但是这样的话肯定会存在字符串中嵌套字符串的问题,所以这时候需要我们sql里面的一个函数concat()用来连接字符串。

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

 可以看到这个时候我们的问题就完美的解决了。

resultMap 和 resultType

前面我们在查询的时候都是对象中的属性名称和数据库中的列名一致的,但是有些时候二者的名称可能会不一致,这个时候如果再去执行代码就会报错。

所以这个时候就需要我们的resultMap将我们类中的属性名称和数据库中的属性名称给一一映射起来。

    <resultMap id="BaseMap" type="com.example.demo.model.UserInfo">
<!--        id主键映射-->
        <id column="id" property="id"></id>
<!--        result普通属性映射-->
        <result column="username" property="name"></result>
    </resultMap>

其中column是数据库中的字段名称,property是我们要映射的类中的属性名称。

多表查询

一对一

上述我们解决了单表查询的问题,但是处理多表查询的时候又该如何去处理呢?

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

这是我们的articlinfo表的参数这里我们加了userinfo的字段,而userinfo又是userinfo表,所以我们在查询的时候需要将userinfo表的内容全部给查询到。这个时候就需要resultMap的映射将我们的内容全部给映射出来。

<?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">
    <resultMap id="BaseMap" type="com.example.demo.model.ArticleInfo">
        <id column="id" property="id"></id>
        <result column="title" property="title"></result>
        <result column="content" property="content"></result>
        <result column="createtime" property="createtime"></result>
        <result column="updatatime" property="updatetime"></result>
        <result column="uid" property="uid"></result>
        <result column="rcount" property="rcount"></result>
        <result column="state" property="state"></result>
        <association property="userInfo" resultMap="com.example.demo.mapper.UserMapper.BaseMap">
<!--            一对一,让userinfo这个字段和userMap中的resultMap相对应-->
        </association>
    </resultMap>
    <select id="getArticleById" resultMap="BaseMap">
        select a.*,u.*
        from articleinfo a left join userinfo u
        on a.uid=u.id
        where a.id=#{id};
    </select>
</mapper>

其中数据库中 的userinfo需要映射到UserMapper中的BaseMap,所以我们还要再去观察一下usermapper中的basemap是否健全。

    <resultMap id="BaseMap" type="com.example.demo.model.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="state" property="state"></result>
        <result column="updatetime" property="updatetime"></result>
    </resultMap>

这里我们将其补全了,然后就开始进行查询。

查询语句:多表查询

    <select id="getArticleById" resultMap="BaseMap">
        select a.*,u.*
        from articleinfo a left join userinfo u
        on a.uid=u.id
        where a.id=#{id};
    </select>

结果:

 乍一看是没问题的但是我们做个对比就发现问题了。

这里面有两个id其中userinfo中的id应该是2但是我们看到我们mybatis中的查询结果却是1,这就是典型的覆盖问题,原因就是我们的查询中有两个id所以想要解决这个问题的话就需要将这两个id给区别开来。

        <association property="userInfo" resultMap="com.example.demo.mapper.UserMapper.BaseMap"
                     columnPrefix="u_"> <!-- 处理两张表中有相同字段的情况 -->
<!--            一对一,让userinfo这个字段和userMap中的resultMap相对应-->
        </association>
    <select id="getArticleById" resultMap="BaseMap">
        select a.*,u.username u_username,u.id u_id,u.updatetime u_updatetime
        from articleinfo a left join userinfo u
        on a.uid=u.id
        where a.id=#{id};
    </select>

查询结果:

 这个时候user中的id就变成了2了。

一对多

用户的属性:

@Data
public class UserInfo {
    private Integer id;
    private String name;
    private String password;
    private String photo;
    private String createtime;
    private String updatetime;
    private int state;
    private List<ArticleInfo> artList;
}
    <resultMap id="BaseMap" type="com.example.demo.model.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="state" property="state"></result>
        <result column="a_updatetime" property="updatetime"></result>
        <collection property="artList" resultMap="com.example.demo.mapper.ArticleMapper.BaseMap" columnPrefix="a_">
<!--            类属性以及他们之间的对应关系-->
        </collection>

    </resultMap>

不同于一对一,一对多用的是collection来处理的。

sql代码:

    <select id="getArticleById" resultMap="BaseMap">
        select a.*,u.username u_username,u.id u_id,u.updatetime u_updatetime
        from articleinfo a left join userinfo u
        on a.uid=u.id
        where a.id=#{id};
    </select>

测试:

    @Test
    void getUserAndArticleById() {
        UserInfo userInfo = userMapper.getUserAndArticleById(2);
        log.info("用户:\n"+userInfo);
    }

动态SQL

if标签

我们在使用数据库的时候不是所有的我们会遇到一种情况,那就是我们有的时候会传一些参数有的时候不会传递,所以我们在进行操作的时候该如何去操作呢?

if标签就是典型的控制我们什么时候需要某个参数什么时候不需要某个参数的,具体用法如下:

    </insert>
    <insert id="add2">
        insert into userinfo(username,password
        <if test="photo!=null">
            ,photo
        </if>
        )
        values(#{name},#{password}
        <if test="photo!=null">
            ,#{photo}
        </if>
        )
    </insert>

这是我们的一个插入语句,我们的photo的字段可有可无所以我们用一个if标签来进行选择是否存在。

    @Test
    void add2() {
        UserInfo user = new UserInfo();
        user.setName("zhangsan");
        user.setPassword("1234");
        user.setPhoto("hhd.jpg");
        Integer userInfo = userMapper.add2(user);
        log.info("用户:"+userInfo);
    }
}

trim标签

最主要的作用,去除 SQL 语句前后多余的某个字符。一共有四个属性:

prefix:表示整个语句块,以prefix的值作为前缀
suffix:表示整个语句块,以suffix的值作为后缀
prefixOverrides:表示整个语句块要去除掉的前缀
suffixOverrides:表示整个语句块要去除掉的后缀

    <insert id="add3">
        insert into userinfo
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="name!=null" >
                username,
            </if>
            <if test="password!=null" >
                password,
            </if>
            <if test="photo!=null" >
                photo,
            </if>
        </trim>
        values
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="name!=null" >
                #{name},
            </if>
            <if test="password!=null" >
                #{password},
            </if>
            <if test="photo!=null" >
                #{photo},
            </if>
        </trim>

    </insert>
    @Test
    void add3() {
        UserInfo user = new UserInfo();
        user.setName("lisi");
        user.setPassword("1234");
        user.setPhoto("hhd.jpg");
        Integer userInfo = userMapper.add2(user);
        log.info("用户:"+userInfo);
    }

where标签

主要作用是实现查询中的 where sql 替换的,它可以实现如果没有任何的查询条件,那么它可以隐藏查询中的 where sql,但如果存在 查询条件,那么会生成 where 的 sql 查询,并且使用 where 标签可以自动去除前面的 and 字符

<select id="getUserById" resultMap="BaseMap">
    select * from userinfo
    <where>
        <if test="id!=null">
            id=#{id}
        </if>
    </where>
</select>

如果不传 id 的话:

    @Test
    void getUserById() {
        UserInfo userInfo = userMapper.getUserById(null);
        System.out.println(userInfo);
        Assertions.assertNotNull(userInfo);
    }

可以看到如果我们传递的是一个null的话,那么此时我们的动态sql中的where标签里面一个参数也没有所以我们的where标签自动取消了。

set标签

set标签用来更新我们的数据,其主要的功能在于可以动态的选择参数,也就是可以选择我们更新一个两个还是三个数据,同时set自带清除后面多余的逗号。

    <update id="update2">
        update userinfo
        <set>
            <if test="name!=null">
                username=#{name},
            </if>
            <if test="password!=null">
                password=#{password},
            </if>
            <if test="photo!=null">
                photo=#{photo},
            </if>
        </set>
        where id=#{id}
    </update>

主要写法如上所示

    @Test
    void update2() {
        UserInfo user = new UserInfo();
        user.setName("xiaohuang");
        user.setPassword("1234");
        user.setId(2);
        int num = userMapper.update2(user);
        Assertions.assertEquals(1,num);
    }

foreach标签

对集合进行遍历时可以使用该标签,foreach 标签有这些属性:

  • collection:绑定⽅法参数中的集合,如 List,Set,Map或数组对象,也就是我们的形参的名称
  • item:遍历时的每⼀个对象(集合中的元素)
  • open:语句块开头的字符串(类似 trim的 prefix)
  • close:语句块结束的字符串(类似 trim的 close)
  • separator:每次遍历之间间隔的字符串(间隔符)
    <delete id="delIds">
        delete from userinfo where id in
        <foreach collection="ids" open="(" close=")" item="id" separator=",">
            #{id}
        </foreach>
    </delete>
    @Test
    void delIds() {
        List<Integer> ids = new ArrayList<>();
        ids.add(11);
        ids.add(15);
        int num = userMapper.delIds(ids);
        log.info("批量删除的个数"+num);
    }


 

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

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

相关文章

低版本CUDA安装/多版本切换/用户级CUDA安装

前言&#xff1a;我想要安装Torch V1.7.1&#xff0c;根据版本信息只有cuda10.2和11.0。但是&#xff0c;本地安装的CUDA版本为11.6和11.2&#xff0c;都不能满足需求。因此需要降低CUDA版本为10.2 本机系统信息 CUDA版本和显卡驱动 nvidia-smi目前CUDA版本为11.6&#xff0c…

7万字省级智慧农业大数据平台项目规划建设方案

1.1 系统总体结构和逻辑结构 XX市智慧农业项目数据中心是全省数据处理加工和数据分析应用的中心&#xff0c;总体上需实现上连省农业厅、下连各级农业、外连市级部门&#xff1b;构建资源整合、互联互通、资源共享的全省统一的数据中心资源库&#xff1b;构建完善的底层支撑平…

Excel常用快捷键,你也可以成为大家口中的大神

掌握复制、粘贴&#xff0c;相信每个职场人都能骄傲地说“自己熟练使用Excel”&#xff0c;果真如此不害臊吗&#xff1f;不出10秒钟&#xff0c;肯定被面试官问得哑口无言。快捷键太多&#xff0c;记住几个常用的&#xff0c;收藏起来&#xff0c;需要的时候过来查看。 “学E…

软考高级架构师-1计算机硬件

目录 1. 前言 & 更新2. CPU组成3.存储器4. 总线1. 前言 & 更新 计算机硬件章节19-21年没考过,在22年真题考过磁盘调度,根据趋势分析,以后考的概率也不大,了解即可。 本节删掉了第一版中的编码、海明码等内容。 2. CPU组成 计算机的基本硬件系统由控制器、运算器…

日常节省 30%计算资源:阿里云实时计算 Flink 自动调优实践

摘要&#xff1a;本文整理自阿里云开发工程师&#xff0c;Apache Flink Contributor 钟旭阳&#xff0c;在 Flink Forward Asia 2022 生产实践的分享。本篇内容主要分为四个部分&#xff1a; 1. 历史背景 2. 框架简介 3. 案例介绍 4. 未来规划 Tips&#xff1a;点击「阅读原文」…

粘包/拆包问题一直都存在,只是到TCP就拆不动了。

• OSI open-system-Interconnection• TCP/IP 5层协议栈 • 应用层和操作系统的边界是 系统调用 &#xff0c;对应到网络编程是socket api• TCP/UDP 概况• TCP粘包问题• 结合TCP/IP报头再回顾&#xff0c;柳暗花明 OSI开放系统互联 定义了网络框架&#xff0c;以层为单位实…

【VSLAM】ORB-SLAM3安装部署与运行

心口如一&#xff0c;犹不失为光明磊落丈夫之行也。——梁启超 文章目录 :smirk:1. ORB-SLAM3介绍:blush:2. 代码安装部署1. 安装ros与opencv2. 安装Pangolin作为可视化和用户界面3. 安装Eigen3一个开源线性库&#xff0c;可进行矩阵运算4. 安装ORB-SLAM3 :satisfied:3. 案例运…

OpenHarmony开发者大会正式召开 百业齐鸣开源共兴​

4月19日&#xff0c;OpenHarmony 开发者大会于北京正式召开。此次会议以“开源正当时&#xff0c;共赢新未来”为主题&#xff0c;邀请了各行各业众多开发者与会&#xff0c;盛况非凡。据了解&#xff0c;此次大会由开放原子开源基金会指导&#xff0c;OpenHarmony 项目群工作委…

依赖引入手册Idea常用设置常用工具手册

目录 数据库相关web应用工具相关postmancanal 测试相关插件相关Idea相关 数据库相关 <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <!--连接池-->…

进程控制(上)

目录 &#xff1a; 1.fork&#xff08;&#xff09;写实拷贝 -- 缺页中断 2.进程终止 1.退出码 2.exit&#xff08;进程终止&#xff09;、_exit() 3.进程等待 1.进程等待的必要性 2.进程如何进入等待 -----------------------------------------------------------------…

MySQL常见的存储引擎

InnoDB&#xff1a;InnoDB是一种兼顾高可靠性和高性能的通用存储引擎&#xff0c;在MySQL 5.5之后&#xff0c;InnoDB是默认的MySQL存储引擎。 特点&#xff1a;1、DML操作遵循ACID模型&#xff0c;支持事务; 2、行级锁&#xff0c;提高并发访问性能; 3、支持外键FOREIGN KEY约…

爬虫框架有Scrapy、BeautifulSoup、Selenium

爬虫框架有Scrapy、BeautifulSoup、Selenium BeautifulSoup比Scrapy相对容易学习。Scrapy的扩展&#xff0c;支持和社区比BeautifulSoup更大。Scrapy应被视为蜘蛛&#xff0c;而BeautifulSoup则是Parser。 1.爬虫基础知识 在开始Python爬虫之前&#xff0c;需要先掌握一些基础知…

C++特殊类设计

文章目录 1.设计一个类&#xff0c;不能被拷贝2.设计一个类&#xff0c;只能在堆上创建对象3.设计一个类&#xff0c;只能在栈上创建对象4.设计一个类&#xff0c;不能被继承5.设计一个类&#xff0c;只能创建一个对象5.1 单例模式5.2 饿汉模式5.3 懒汉模式5.4 两种模式的析构函…

操作系统进程线程(二)—父子进程、僵尸进程、孤儿进程、进程终止、守护进程

父子进程、进程组、作业、会话 父进程 已创建一个或者多个进程 子进程 fork创建的。这个函数被调用一次但是返回两次&#xff0c;子进程返回0&#xff0c;父进程返回子进程id。 fork之后&#xff0c;操作系统会赋值一个与父进程完全相同的子进程&#xff0c;虽然是父子关系…

微软 BingChat,全面开放使用 !

前两天&#xff0c;微软突然官宣全面开放BingChat&#xff1a; 无需任何等待。只需注册一个账户&#xff0c;首页即可体验。 更关键的是&#xff0c;还有一大堆堪称“家底”的新功能来袭&#xff01; 支持100种语言多模态输出、持续聊天且记录可随时导出、类ChatGPT插件功能…

企业内部信息太琐碎怎么办?选择一款在线工具制作内部知识库

在一个企业内部&#xff0c;信息的传递和沟通是非常重要的。企业内部的信息往往比较杂碎&#xff0c;包括各种文件、资料、报告、流程等等&#xff0c;如果不加以整理和管理&#xff0c;就会给企业的工作带来困难和不便。因此&#xff0c;选择一款在线工具制作内部知识库是非常…

桌面虚拟化的优势

启用基于云的虚拟桌面基础架构 &#xff08;VDI&#xff09; OpenText™ Exceed TurboX™ &#xff08;ETX&#xff09; 长期以来一直是虚拟化在 Linux 主机上运行的图形要求苛刻的软件的黄金标准。ETX 最新版本&#xff08;12.5&#xff09;增加了许多Microsoft Windows功能&…

JetPack WindowManager详解

一、JetPack架构 Android Jetpack 是Android官方提供的一套组件、工具和指导,可以帮助开发者摆脱编写样板代码并简化复杂任务,并且Jetpack组件提供向后的兼容性, 能够帮助开发者更快的开发更稳定且易维护的应用。 Jetpack大体分为4类:Architecture(架构)、Foundationy(…

项目经理如何及时掌控项目进度?

延迟是指超出计划的时间&#xff0c;而无法掌控则意味着管理者对实际情况一无所知。 为了解决这些问题&#xff0c;我们需要建立好的制度和沟通机制。例如使用项目管理软件来跟踪进度、定期开会并避免沟通障碍等。 管理者可以建立相关制度&#xff1a; 1、建立进度记录制度。…

SpringBoot启用web模拟测试(二)

1.web环境模拟测试 设置测试端口&#xff08;见SpringBoot启用web模拟测试&#xff08;一&#xff09;&#xff09; 模拟测试启动&#xff08;见SpringBoot启用web模拟测试&#xff08;一&#xff09;&#xff09; 测试模拟匹配&#xff08;各组成信息皆可配置&#xff09; …