Mybatis 的多种标签使用以及 Spring 框架单元测试(下)

简介: Mybatis 的多种标签使用以及 Spring 框架单元测试(下)

三. Mybatis 多表查询



多表查询在数据库操作中是非常常见的但是又有点难的操作. 它体现的是一种一对多的思维. 什么是一对多呢 ? 比如一个用户可以写多篇文章, 但是一篇文章却只能有一个作者 ( 假设没有第二作者 ) , 而这里的一个用户对应多篇文章就是一对多的关系.


1. 一对一多表查询


比如下面这个场景 : 在文章表中根据文章 id 查询用户 ( 一对一的多表关系 即一篇文章只有一个作者 )

先来看我们的文章表有什么字段 :

60680e1fc22601310f271c23646659c4.png


但是我们发现要返回用户的话, 这个表里的字段和用户表里有些不太一样, 光返回这个文章对象的话, 那就没有用户名可以展示了. 这样不知道是谁的文章了.

e94dae901c96e550e5e92effdfb5a0c2.png


为了解决返回对象的问题, 需要在文章表中增添用户表里的 username 字段才能满足需求, 但是遵循标准设计规则来说, 是不能直接在 articleInfo 用户表上添加的, 这不利于单一性设计原则, 遵循设计标准我们可以添加一个 ArticleInfoVO 的文章扩展实体类


  • 建立文章表的实体类
@Data
public class ArticleInfo {
    private int id;
    private String title;
    private String content;
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    private int uid;
    private int rcount;
    private int state;
}


  • 建立文章表的扩展实体类
@Data
public class ArticleInfoVO extends ArticleInfo {
    // 建立在文章表的基础上扩展了 username 属性
    private String username;
}


由于我们已经更换操作对象, 从用户操作变成了文章操作, 因此也是需要重新建立 Mybatis 的 Mapper 和 XML 的


  • 建立 ArticleMapper 接口类

实现接口方法, 根据文章 id 查询用户

@Mapper
public interface ArticleMapper {
    ArticleInfoVO getDetailArticle(@Param("id") Integer id);
}


  • 建立 ArticleMapper 的接口实现 ArticleMapper.xml 并构造 SQL 语句 ( 除了多表查询, 其他的都是建立一个新的 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.ArticleMapper">
<!--  多表联合查询  -->
    <select id="getDetailArticle" resultType="com.example.demo.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>


重点来解释一下这个SQL 语句 :

  • a.* - 表示 a 表的全部字段, 重命名后表示文章表的所有字段
  • u.username - 表示 u 表的 username 字段, 重命名后表示用户表的 username 字段
  • articleinfo a - 表示给文章表重命名为 a 表
  • userinfo u - 表示给用户表重命名为 u 表
  • u.id=a.uid - 表示用户的 id = 文章的文章 id ( 因为我们是根据文章 id 查询, 要确定文章的归属人
  • a.id=#{ id } - 表示文章的 id = 外部传来的参数 id


结合上面的解释, 我们再来看这个多表查询就清楚多了. 也就是我们的业务要求根据传来的 id 查询文章并且返回文章和文章的归属人( 也就是这篇文章的作者 )

image.png


建立单元测试方法

@SpringBootTest
class ArticleMapperTest {
    @Autowired
    private ArticleMapper articleMapper;
    @Test
    void getDetailArticle() {
        ArticleInfoVO articleInfoVO = articleMapper.getDetailArticle(1);
        System.out.println(articleInfoVO);
    }
}


执行测试方法后可以看到它查到一行, 并且信息也有, 但是我们打印的 ArticleInfoVO 不是继承了 ArticleInfo 嘛 ? 为什么没有呢 ?

image.png

而且数据库中这一条信息和我们所得到的信息是一致的, 但就是获取的 ArticleInfoVO 里面没有

99aaae9bbed2282a1c39c6384134e163.png

检查后发现, 代码是没有问题的, 并且它也正确执行了, 但就是打印对象 articleInfoVO 结果没有继承到相应对象 ArticleInfo, 我们可以到缓存中看看这个类是怎么执行的

a8b5a9a29feaa545eb9c318b2a115e98.png

从它最后一条语句执行上可以看到, 它最后执行了我们的打印对象 toString() 方法, 并且它只返回了 this.getUsername 也就是获取用户名. 而我们打印的也恰好就是用户名没有其他信息

image.png


这个问题想必大家已经很熟悉了, 肯定是重写我们的 toString() 方法了, 并且需要加上父类的toString() 这样才能让我们的 ArticleInfoVO 打印信息时有父类信息

214cc987c63ac8897b688e486b3781cb.png

@Data
public class ArticleInfoVO extends ArticleInfo {
    // 建立在文章表的基础上扩展了 username 属性
    private String username;
    @Override
    public String toString() {
        return "ArticleInfoVO{" +
                "username='" + username + '\'' +
                "} " + super.toString();
    }
}

再次执行测试方法, 这次就有了我们的父类的信息展现了

image.png


2. 一对多多表查询


刚刚我们实现的是一个一对一的多表查询, 但是如果我们现在查询的是一个作者的全部文章呢 ?


这时候就是一对多的关系了, 毕竟一个作者有可能是有多篇文章的, 下面就来实现一下

接口方法实现, 有多篇文章对象, 因此用 List

List<ArticleInfoVO> getAllArticleByUserId(@Param("id") Integer id);


XML 实现 : 这里需要注意的是, 此处我们查询的还是文章, 因此主表依然是文章. 并且现在是一个作者有多篇文章, 哪文章是怎么判定归属人的至关重要, 也就是通过文章表的 uid 来判断是谁写的.


<select id="getAllArticleByUserId" resultType="com.example.demo.entity.vo.ArticleInfoVO">
    select a.*, u.username from articleinfo a
    left join userinfo u on u.id=a.uid // 这里需要注意是在判定文章归属的情况下
    where u.id=#{id} // 此处是根据用户 id 查询它有多少文章
</select>


生成单元测试方法

@Test
void getAllArticleByUserId() {
    List<ArticleInfoVO> list = articleMapper.getAllArticleByUserId(1);
    list.stream().forEach(System.out::println); // 输出流 lambda 表达式
}


运行后发现, 可以查到两条数据, 我们上数据库中查看是否正确

1be3e36dc828901cb8fecf65e6b35028.png


可以看到, 文章表中两篇文章都是 id 为 1 的 admin 发布的.

aa96d94fe9b19b88098cca3e58333f7c.png


四. 动态 SQL



1. 什么是动态 SQL


动态 SQL 拼接, 在之前学习 JDBC 的过程中想必都不陌生. 但是 JDBC 去拼接 SQL 的时候, 你是否也遇到过拼接时逗号没处理 ? 空格加错了 ? 这是非常容易处理错的, 现在 Mybatis 对 JDBC 进行了进一步封装, 操作动态 SQL 就变得简单起来了, 不会在容易出之前的错误, 让你摆脱痛苦.


比如在注册时, 有用户添加头像, 这并不是一个必填选项. 你可以选择注册时就添加, 也可以选择不添加直接注册. 当你选择了不添加头像直接注册时, 当你注册登录过后会给你一个默认的初始头像.


一般我们存储照片都是在数据库中存其图片路径, 并通过查询后返回给前端设置到页面中. 当用户注册时, 头像这一栏是不确定的, 如果你注册时不传头像也就是此时前端传来的是一个 null 的空置, 如果我们直接进行拼接那么最终你得头像就会无法正常显示. 这并不是我们想要的, 我们想要的是即使前端没有传来头像为 null 我们需要判断过后让它显示为默认头像路径.


这就是一个简单的动态 SQL 问题. 如果前端传了头像 null 我就让它不拼接. 如果前端传了非 null 我就让它拼接传来的图片路径


2. Mybatis 动态标签


针对于上面的问题, 我们要做的就是判断用户传来的 photo 这个字段是否非空从而做出不同处理. 在 JDBC 中如果进行这个操作, 要做的就是写两个 SQL 语句, 对这两种情况下分别进行处理. 但是我们说在 Mybatis 中可以更容易解决这件事, 就是因为它的动态标签. Mybatis 中的条件判断标签便可以让刚刚的问题变成一个 SQL 语句


2.1 <if> 标签


// 根据是否有 photo 字段新增用户
int insertUserByPhoto(UserEntity user);


xml 中使用 < if > 条件动态标签, 那么既然是条件就需要写判断条件, 因此这里的 test 属性是必须要设置的.

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


对于上面的 SQL 语句, 初看是十分突兀的. 下面就解释一下这个 SQL 语句是什么意思

d011a7418373344c42cd75fb9a8174ec.png


那么, 有有人会有疑惑了, 如果第一个 < if > 标签成立, 第二个 < if > 标签不成立的情况下, 这个 SQL 语句肯定是错误的.


对于这个疑惑, 初学是很容易犯的. 如果前一个条件成立后一个不成立的情况下, 的确是肯定错误的. 但是需要注意 test 属性中的 photo 它是属性, 并非属性值 ! ! ! 而我们的 #{photo} 它才是属性值, 而 photo 它是数据库中的字段. 因此如果用户的头像没有传过来, 那么这个属性它就是空的, 只要第一个条件是成立的第二个条件肯定也是成立的. 不存在其中一个存在另一个不存在的情况.


因此这个动态 SQL 语句就可以分为两种情况了, 并且我的数据中设置 photo 默认值是 ’ ’ 的, 并非默认的是 null

1b0e93281bb77102bff4d83a703d312f.png


建立单元测试方法测试一下

  • 条件成立时
@Test
void insertUserByPhoto() {
    UserEntity user = new UserEntity();
    user.setUsername("xiaoqi");
    user.setPassword("123");
    user.setPhoto("img");
    userMapper.insertUserByPhoto(user);
    System.out.println(user);
}


此时传过来了 img 这张图片, 说明有 photo 字段, 并且 photo 的属性值就是 img

47e669120c6d5db688a44b3672c92bd0.png

由于 < if > 标签的原因, 此时的 SQL 语句为

insert into userinfo(username, password, photo) values('xiaoqi', '123', 'img')

1ed547c7be66ed98b2edd8126ae22104.png


  • 条件不成立时

此时我并未设置 photo , 因此它默认传过去的是 null

@Test
void insertUserByPhoto() {
    UserEntity user = new UserEntity();
    user.setUsername("xiaoliu");
    user.setPassword("123");
    userMapper.insertUserByPhoto(user);
    System.out.println(user);
}


传过来的是 null 说明没有这个字段, 此时我们插入数据库中的对象预期 photo 这一栏应该就是我们默认的 ’ ’ 空, 并非 null

7e6c3958f3fbe8b1a54e67e1feccf884.png


可以看到, 我们没有传 photo 此时默认传来的是 null, 此时 <if> 标签就起作用了. SQL 语句为

insert into userinfo(username, password) values('xiaoliu', '123')

7d826406e04e804ba3cffc28bdd57366.png


通过上面的这个例子, 对于动态 SQL 是什么简单来说就是根据不同的条件拼接不同的 SQL 语句, 从而解决复杂的场景下对于数据库的操作.


2.2 <trim> 标签


对于刚刚的 SQL 语句, 用 <if> 标签可以很好的解决, 它只有一个非必传参数. 但事实上对于一个用户来说, 有很多都是非必传的, 情况是非常复杂的. 极端情况下我们刚刚的用户名或者密码以及头像都不传. 这个时候就出问题了

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


如果此时 username 和 photo 都为一个非必传参数, 我们用 <if> 标签重新构建 SQL

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



极端情况下是什么, 我的 username 和 photo 都不传过来. 那么现在再来看这个 SQL 语句会变成什么样 ?


insert into userinfo(password,) values(#{password},)


发现了嘛 ? 这完全就是一个错误的 SQL 语句, 因为逗号的问题, 这种情况下使用 <if> 标签就无法解决了. 对于这种极端特殊情况下, Mybatis 为我们提供了给为强大的 <trim> 标签.

看到 trim 大家应该很熟悉, 我们常用来去除空格的操作就是使用它. 在 Mybatis 中 <trim> 标签也是用来去除什么的操作


先来了解一下 <trim> 标签, 它的属性还是比较多的

292999ea54dc7de3940357abca9c93be.png


  • prefix : 表示整个语句块内以 prefix 设置的属性值作为前缀
  • suffix : 表示整个语句块内以 suffix 设置的属性值为后缀
  • prefixOverrides : 表示整个语句块内要去掉的前缀值
  • suffixOverrides : 表示整个语句块内要去掉的后缀值


对于刚刚的问题, 我们就可以设置整个语句的后缀属性值为 suffix = “,” 然后选择去除掉整个语句块内的后缀值 suffixOverrides


下面来试试, 由于我的 username 在数据中为一个必传字段, 因此无法验证, 这里我选择将它更改为 state 字段, 它的默认值设置为 “1”, 并非为空时默认的 0

bdd7dc0f4fc5901df3b26cf10a306a5c.png


对于这个的 SQL 语句为 :

<insert id="insertUserByPhotoAndState">
    insert into userinfo(
    <trim suffixOverrides=",">
        username, password,
        <if test="photo != null and photo != '' ">
            photo,
        </if>
        <if test="state != null and state != '' ">
            state
        </if>
    </trim>
    ) values(
    <trim suffixOverrides=",">
        #{username}, #{password},
        <if test="photo != null and photo != '' ">
            #{photo},
        </if>
        <if test="state != null and state != '' ">
            #{state}
        </if>
    </trim>
    )
</insert>


经过分析, 出现逗号在末尾多余的情况都是在 userinfo ( ) 和 values ( ) 的括号里面, 因此我们的 <trim> 标签的起作用范围就是两个括号之间. 因此只需要加在这两部分中间就可以了,表示我们要去掉的就是括号内的语句的最后一个逗号


生成单元测试方法看看 :

  • username 和 password 必传, photo 和 state 我们都不传入. 此时整个 SQL 语句就变成了


inser into userinfo(username, password,) values(#{username}, #{password},)


可以看到, 两个括号内都是多了后面的一个逗号的. 如果测试能通过, 说明我们的 是起作用了的.

@Test
@Transactional
void insertUserByPhotoAndState() {
    UserEntity user = new UserEntity();
    user.setUsername("老八");
    user.setPassword("123");
    user.setPhoto("img");
    userMapper.insertUserByPhotoAndState(user);
    System.out.println(user);
}


执行后看到 SQL 的执行语句, 即使我们刚刚没有传入 photo 和 state 结尾多出来了逗号, 但由于 < trim > 标签的存在, 帮我们解决了这个问题.

0fc80fcff2b4c9d2c4e78a703c31d0b1.png


  • username 和 password 为必传, photo 传入而 state 不传, 此时 SQL 语句为 :


insert into userinfo(username, password, photo,) values(#{username}, #{password}, #{photo},)


可以看到, 在 userinfo( ) 和 values( ) 两个括号里面仍然是多余了结尾的逗号, 看看这次 还能解决嘛 ?


可以看到, 同样是可以解决的.

73c74cae640936e5dd460e6c4c7eeec9.png


当然, 除了上面的写法外, 我们看到 <trim> 标签里还有其他几个属性. 在这同样是可以使用的.


比如, 现在连括号我们都可以不用写了, 括号都让它给我们自己拼接

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


此时整个 SQL 语句的意思就变成了 : 在判断 <if> 标签后, 如果有内容则在这之前加上 “(” , 并且在语句结束后加上 " ) ". 去掉后缀是有它就给你去掉, 没有也不会影响它正确执行.


比如我在 #{state} 后面还加了逗号, 此时我的参数都传入的情况下, values( ) 括号里面回多出来一个逗号.

a402190cc5bdb163664d513e54054fbb.png


@Test
@Transactional
void insertUserByPhotoAndState() {
    UserEntity user = new UserEntity();
    user.setUsername("老八");
    user.setPassword("123");
    user.setPhoto("img")    ;
    user.setState(1);
    userMapper.insertUserByPhotoAndState(user);
    System.out.println(user);
}

eeed77b058ab18df76e36da3862fec41.png


可以看到, < trim > 标签它是非常好用的. 无论你是要去掉前缀还是后缀它都可以实现. 并且在去掉之前主动加上前缀或者后缀来帮我们构建 SQL 也是可以的 ( 比如我刚刚实现的执行前加上前缀" ( ", 执行后加上后缀 " ) " 这个操作 ). 可以根据根据自己的业务需求进行选择.


2.3 <where> 标签


< where > 标签很明显, 就是一个条件标签. 和之前学的意思是一样的. 但在 Mybatis 中的 < where > 标签和 SQL 中直接使用 where 还有很多的不同.


下面来看这个场景, 我们上面提到的同样场景 : 根据文章的 id 和 文章标题模糊查询文章. 并且 id 和 文章的标题都是非必传的. 显而易见这又是一个动态 SQL

// 根据 id 和 title 来筛选文章
// 我们此处不用扩展类里的 username 字段, 此处返回 articleInfo 对象也行
List<ArticleInfoVO> getArticleByIdAndTitle(@Param("id") Integer id, 
                                           @Param("title") String title);


根据需求构建 SQL 语句

<select id="getArticleByIdAndTitle" resultType="com.example.demo.entity.vo.ArticleInfoVO">
    select * from articleinfo
    where
    <if test="id != null and id != '' ">
        id=#{id} and
    </if>
    <if test="title != null and title != '' ">
        title like concat('%', #{title}, '%')
    </if>
</select>


稍加一分析, 可以发现问题, 如果此时只传入 id 不传入 titile 那么, 结尾会多出来 end, 因此我们可以使用之前使用的 < trim > 标签来解决这个问题

<select id="getArticleByIdAndTitle" resultType="com.example.demo.entity.vo.ArticleInfoVO">
    select * from articleinfo
    where
    <trim suffixOverrides="and">
        <if test="id != null and id != '' ">
            id=#{id} and
        </if>
        <if test="title != null and title != '' ">
            title like concat('%', #{title}, '%')
        </if>
    </trim>
</select>


即使这样, 还是会有问题. 极端情况下当我们都不传入 id 和 title 的时候, 这时候 where 条件就空了, 整个 SQL 语句变成了 :


select * from articleinfo where


没有 where 条件, 它肯定是一个错误的 SQL 语句, 是无法执行的. 那么这个问题如何解决 ?


不难想到, 可以手动构建 where 条件 1=1 进去, 稍加调整一下 and 位置皆可以了. 这时候的 and 就需要重新考虑去除的位置为开头去除前缀了.

<select id="getArticleByIdAndTitle" resultType="com.example.demo.entity.vo.ArticleInfoVO">
    select * from articleinfo
    where 1 = 1
    <trim prefixOverrides="and">
        <if test="id != null and id != '' ">
            and id=#{id} 
        </if>
        <if test="title != null and title != '' ">
            and title like concat('%', #{title}, '%')
        </if>
    </trim>
</select>


这样也能解决, 但还有更好的办法. 现在的问题就是这个 where 的问题, 没有条件的时候我们希望它不出现. 它完全可以利用我们之前学的 < trim > 标签. 当我们的 < trim > 标签里没有内容的时候, 它就不会给我们拼接 where, 也就不会出现这个问题了.


<select id="getArticleByIdAndTitle" resultType="com.example.demo.entity.vo.ArticleInfoVO">
    select * from articleinfo
    <trim prefix="where" suffixOverrides="and">
        <if test="id != null and id != '' ">
            id=#{id} and
        </if>
        <if test="title != null and title != '' ">
            title like concat('%', #{title}, '%')
        </if>
    </trim>
</select>

一波三折过后, 这个 SQL 语句目前已经使我们能想到的最优情况了. 先来看看数据中文章内容


image.png


建立单元测试 :

  • id 和 titile 都为不传为 null 的情况下
@Test
@Transactional
void getArticleByIdAndTitle() {
    List<ArticleInfoVO> list = articleMapper.getArticleByIdAndTitle(null, null);
    list.stream().forEach(System.out::println);
}


此时的 SQL 语句为 :


select * from articleinfo


我们预期是这样的, < tirm > 标签自动为我们拼接 where 并且判断是否去掉 and, 最后执行的是全表查询


image.png


  • 只传 id 不传入 title
@Test
@Transactional
void getArticleByIdAndTitle() {
    List<ArticleInfoVO> list = articleMapper.getArticleByIdAndTitle(1, null);
    list.stream().forEach(System.out::println);
}


此时的 SQL 语句为 :


select * from articleinfo where id=1


此时我们查找的就是文章表中 id 为 1 的所有文章. 可以看到是可以正确查到的.

c72fb48143ef28d4fcc98245e6951213.png

对于我们刚刚解决这个需求, 可谓是废了九牛二虎之力, 改了又改才能实现. 但是在 Mybatis 中为我们提供了 < where > 标签来避免这样的窘况.

<select id="getArticleByIdAndTitle" resultType="com.example.demo.entity.vo.ArticleInfoVO">
    select * from articleinfo
    <where>
        <if test="id != null and id != '' ">
            id=#{id} 
        </if>
        <if test="title != null and title != '' ">
            and title like concat('%', #{title}, '%')
        </if>
    </where>
</select>


为什么说它解决了我们的窘况呢 ? 在这里如果使用 < where > 标签, 当里面没有内容的时候, 会自动替我们舍弃 where 也就可以解决 where 没有条件时候的问题. 同时 < where > 标签还有个特点, 就是会替我们去除前缀. 什么意思呢 ? 来看看

<select id="getArticleByIdAndTitle" resultType="com.example.demo.entity.vo.ArticleInfoVO">
    select * from articleinfo
    <where>
        <if test="id != null and id != '' ">
            id=#{id}
        </if>
        <if test="title != null and title != '' ">
        <!-- 这里按照之前, 我们应该使用 <trim> 标签去除前缀 and -->
            and title like concat('%', #{title}, '%')
        </if>
    </where>
</select>


当 id 不传入, 只传入 title 的时候, SQL 语句变成了 :


select * from article where and title like concat('%, #{title}, '%')


很明显这是一个错误的语句, 但是我们上面执行的时候, 它是可以正确执行并查找到的. 就是因为这里的 <where> 标签的特点自动为我们去除了前缀的 and


总的来说, < where > 标签的功能等同于 < trim prefix=’ where ’ prefixOverrides = " and ">< /trim > 是非常强大和方便的条件标签.


3. <set> 标签


对于设置字段属性值时, 通常在 MySQL 中我们使用的是 set 语句, 而在 Mybatis 的动态 SQL 中我们使用的是 < set > 标签


PS : 对于 set 来说通常是需要结合条件语句的. 对于 set 语句而言, 要求至少是有一个字段被设置的, 否则这就是一个错误的语句.


上面的是什么意思呢 ? 比如 username password title 都是非必传的. 但是在使用 < set > 标签去更新字段内容时, 要求 username password title 中必须有一个是要传过来的. 就像三个人轮休一样. 可以商量谁不来, 但是一定要有一个人来. ! ! !


我们可以看看这个 SQL 语句, 如果 username 不传, 那么就是一个错误的语句 因为 set 内容为空, 因此使用 set 时需要格外注意, 一定是即使都是非必传也要有一个一定可以传过来的或者其中一个非必传的传过来.


update articleinfo set username='zhangsan' where id=1


看这个需求, 根据 id 修改篇文章的 title 和 content. 通常在用户修改文章时, 可能只修改内容也可能只修改标题都是不确定的. 因此我们可以使用前面说到的 < set > 标签来解决这个问题

// 根据 id 修改指定文章
int updateById(ArticleInfo articleInfo);
<update id="updateById">
    update articleinfo
    <set>
        <trim suffixOverrides=",">
            <if test="title != null and title != '' ">
                title=#{title},
            </if>
            <if test="content != null and content !='' ">
                content=#{content}
            </if>
        </trim>
    </set>
    where id=#{id}
</update>

建立单元测试方法 :

  • 验证都不传入时, set 语句是否正确
@Test
@Transactional
void updateById() {
    // 构建文章对象
    ArticleInfo articleInfo = new ArticleInfo();
    articleInfo.setId(1);
    int row = articleMapper.updateById(articleInfo);
    System.out.println("更新行数 : " + row);
}


执行后可以看到, set 后面没有被设置的内容是一个错误的 SQL 语句的. 因此在使用 < set > 标签的时候一定要注意, 无论如何都要传入一个设置内容.

54ec2438fc78109d5097dbfd10c257f3.png


  • 只传入 titile 不传入 content

image.png


< set > 和 < where >标签一样, 也有一个小特点, 来看看这个 SQL 语句

<update id="updateById">
    update articleinfo
    <set>
        <if test="title != null and title != '' ">
            title=#{title},
        </if>
        <if test="content != null and content !='' ">
            content=#{content}
        </if>
    </set>
    where id=#{id}
</update>


相比于上面, 我给 < tirm > 标签去掉了. 哪会不会只传入 titile 不传入 content 的时候没法去掉后缀的逗号而报错呢 ?

image.png


结果恰恰相反, 它反而正确执行了. 这就是因为 < set > 标签的特点, 它会帮我们自动去掉 < set >标签语句内的后缀的逗号


因此, <set> 标签等同于 <trim prefix=“set” suffixOverrides=“,”>


4. <foreach> 标签


foreach 在 Java 中相比不陌生, 经常在增强循环中使用, 对集合进行遍历.


在数据库操作中, 常用于批量操作. 比如批量删除这个操作.( 一般不用于批量增加, 新增风险很大容易错 ). 来看看在文章表中, 批量删除指定 id 的文章.

// 删除指定 uid 的全部文章
int deleteByUid(List<Integer> list);


<delete id="deleteByUid">
      delete from articleinfo
      where id in
      <foreach collection="list" item="aid" open="(" close=")" separator=",">
          #{aid}
      </foreach>
</delete>


这个< foreach > 标签一看非常的陌生, 和我们之前的 <trim>标签一样, 有很多属性, 来看看


collection : 接口方法参数中指定的集合, 如 List、Set、Map、或者数组对象

item : 遍历时的每一个对象

open : 语句块开头拼接的字符串

close : 语句块结尾拼接的字符串

separtor : 每次遍历之间间隔的字符串


结合我们的接口方法, 这个 SQL 语句的含义就清晰了 :

image.png


进行单元测试 :

  • 传入的 list 为空时
@Test
@Transactional
void deleteByUid() {
    List<Integer> list = new ArrayList<>();
    int row = articleMapper.deleteByUid(list);
    System.out.println("删除数 : " + row);
}


image.png


当 list 当中没有对象时, 使用<foreach> 就会报错, 这一点和 < set > 标签是非常像的


PS : < foreach > 标签和 <set> 标签一样, 都必须有一个条件. 也就是便利的集合里必须有一条. 否则它就是一个错误的语句.


集合中至少传入一个数据时


@Test
@Transactional
void deleteByUid() {
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);
    int row = articleMapper.deleteByUid(list);
    System.out.println("删除数 : " + row);
}

image.png


五. 总结



对于 Mybatis 还有很多需要学习的, 除了掌握这些基本的以外, 如果还想更加深入的了解 Mybatis 中的其他内容以及更多的动态标签, 可以去 Mybatis 官网 查看

相关文章
|
22天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
1月前
|
Java API 数据库
Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐
本文通过在线图书管理系统案例,详细介绍如何使用Spring Boot构建RESTful API。从项目基础环境搭建、实体类与数据访问层定义,到业务逻辑实现和控制器编写,逐步展示了Spring Boot的简洁配置和强大功能。最后,通过Postman测试API,并介绍了如何添加安全性和异常处理,确保API的稳定性和安全性。
36 0
|
25天前
|
测试技术 C# 数据库
C# 单元测试框架 NUnit 一分钟浅谈
【10月更文挑战第17天】单元测试是软件开发中重要的质量保证手段,NUnit 是一个广泛使用的 .NET 单元测试框架。本文从基础到进阶介绍了 NUnit 的使用方法,包括安装、基本用法、参数化测试、异步测试等,并探讨了常见问题和易错点,旨在帮助开发者有效利用单元测试提高代码质量和开发效率。
136 64
|
26天前
|
前端开发 Java 数据库连接
Spring 框架:Java 开发者的春天
Spring 框架是一个功能强大的开源框架,主要用于简化 Java 企业级应用的开发,由被称为“Spring 之父”的 Rod Johnson 于 2002 年提出并创立,并由Pivotal团队维护。
43 1
Spring 框架:Java 开发者的春天
|
18天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
36 2
|
17天前
|
消息中间件 NoSQL Java
springboot整合常用中间件框架案例
该项目是Spring Boot集成整合案例,涵盖多种中间件的使用示例,每个案例项目使用最小依赖,便于直接应用到自己的项目中。包括MyBatis、Redis、MongoDB、MQ、ES等的整合示例。
68 1
|
25天前
|
Java 数据库连接 开发者
Spring 框架:Java 开发者的春天
【10月更文挑战第27天】Spring 框架由 Rod Johnson 在 2002 年创建,旨在解决 Java 企业级开发中的复杂性问题。它通过控制反转(IOC)和面向切面的编程(AOP)等核心机制,提供了轻量级的容器和丰富的功能,支持 Web 开发、数据访问等领域,显著提高了开发效率和应用的可维护性。Spring 拥有强大的社区支持和丰富的生态系统,是 Java 开发不可或缺的工具。
|
1月前
|
Java 数据库连接 Maven
mybatis使用一:springboot整合mybatis、mybatis generator,使用逆向工程生成java代码。
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和MyBatis Generator,使用逆向工程来自动生成Java代码,包括实体类、Mapper文件和Example文件,以提高开发效率。
108 2
mybatis使用一:springboot整合mybatis、mybatis generator,使用逆向工程生成java代码。
|
1月前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
54 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
1月前
|
前端开发 Java Apache
Springboot整合shiro,带你学会shiro,入门级别教程,由浅入深,完整代码案例,各位项目想加这个模块的人也可以看这个,又或者不会mybatis-plus的也可以看这个
本文详细讲解了如何整合Apache Shiro与Spring Boot项目,包括数据库准备、项目配置、实体类、Mapper、Service、Controller的创建和配置,以及Shiro的配置和使用。
300 1
Springboot整合shiro,带你学会shiro,入门级别教程,由浅入深,完整代码案例,各位项目想加这个模块的人也可以看这个,又或者不会mybatis-plus的也可以看这个