• Mybatis-Plus入门(1)


    单表的CRUD功能代码重复度很高,也没有什么难度。而这部分代码量往往比较大,开发起来比较费时。因此,目前企业中都会使用一些组件来简化或省略单表的CRUD开发工作。目前在国内使用较多的一个组件就是MybatisPlus.
    官方网站如下:
    简介 | MyBatis-Plus
    当然,MybatisPlus不仅仅可以简化单表操作,而且还对Mybatis的功能有很多的增强。可以让我们的开发更加的简单,高效。

    1.CRUD快速入门

    一、Spring Boot整合Mybatis Plus

    第一步:通过maven坐标引入依赖

    
    <dependency>
      <groupId>com.baomidougroupId>
      <artifactId>mybatis-plus-boot-starterartifactId>
      <version>3.5.3.1version>
    dependency>
    
    <dependency>
      <groupId>com.mysqlgroupId>
      <artifactId>mysql-connector-jartifactId>
      <scope>runtimescope>
    dependency>
    
    <dependency>
      <groupId>org.projectlombokgroupId>
      <artifactId>lombokartifactId>
      <optional>trueoptional>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    第二步:application配置数据源及日志输出级别

    # 配置数据源
    spring:
      datasource:
        url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: 123456
    # 配置日志
    logging:
      level:
        com.itheima: debug
      pattern:
        dateformat: HH:mm:ss
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    第三步:配置Mybatis的Mapper类文件的包扫描路径

    @MapperScan("com.itheima.mp.mapper")
    @SpringBootApplication
    public class MpDemoApplication {
        public static void main(String[] args) {
            SpringApplication.run(MpDemoApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    二、编码构建实体和Mapper

    编写实体类User.java

    @Data
    @TableName(value = "user", autoResultMap = true)
    public class User {
    
        /**
         * 用户id
         */
        @TableId
        private Long id;
    
        /**
         * 用户名
         */
        @TableField("`username`")
        private String username;
    
        /**
         * 密码
         */
        private String password;
    
        /**
         * 注册手机号
         */
        private String phone;
    
        /**
         * 详细信息
         */
        @TableField(typeHandler = JacksonTypeHandler.class)
        private UserInfo info;
    
        /**
         * 使用状态(1正常 2冻结)
         */
        private UserStatus status;
    
        /**
         * 账户余额
         */
        private Integer balance;
    
        /**
         * 创建时间
         */
        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
        private LocalDateTime createTime;
    
        /**
         * 更新时间
         */
        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
        private LocalDateTime updateTime;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    编写Mapper类UserMapper.java

    @Mapper
    @Repository
    public interface UserMapper extends BaseMapper<User> {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.常见注解

    在刚刚的入门案例中,我们仅仅引入了依赖,继承了BaseMapper就能使用MybatisPlus,非常简单。但是问题来了:
    MybatisPlus如何知道我们要查询的是哪张表?表中有哪些字段呢?

    大家回忆一下,UserMapper在继承BaseMapper的时候指定了一个泛型:
    image.png
    泛型中的User就是与数据库对应的PO.

    MybatisPlus就是根据PO实体的信息来推断出表的信息,从而生成SQL的。默认情况下:

    • MybatisPlus会把PO实体的类名驼峰转下划线作为表名
    • MybatisPlus会把PO实体的所有变量名驼峰转下划线作为表的字段名,并根据变量类型推断字段类型
    • MybatisPlus会把名为id的字段作为主键

    但很多情况下,默认的实现与实际场景不符,因此MybatisPlus提供了一些注解便于我们声明表信息。

    2.1.@TableName

    说明:

    • 描述:表名注解,标识实体类对应的表
    • 使用位置:实体类

    示例:

    @TableName("user")
    public class User {
        private Long id;
        private String name;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    TableName注解除了指定表名以外,还可以指定很多其它属性:

    属性类型必须指定默认值描述
    valueString“”表名
    schemaString“”schema
    keepGlobalPrefixbooleanfalse是否保持使用全局的 tablePrefix 的值(当全局 tablePrefix 生效时)
    resultMapString“”xml 中 resultMap 的 id(用于满足特定类型的实体类对象绑定)
    autoResultMapbooleanfalse是否自动构建 resultMap 并使用(如果设置 resultMap 则不会进行 resultMap 的自动构建与注入)
    excludePropertyString[]{}需要排除的属性名 @since 3.3.1

    2.2@TableId

    说明:

    • 描述:主键注解,标识实体类中的主键字段
    • 使用位置:实体类的主键字段

    示例:

    @TableName("user")
    public class User {
        @TableId
        private Long id;
        private String name;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    TableId注解支持两个属性:

    属性类型必须指定默认值描述
    valueString“”表名
    typeEnumIdType.NONE指定主键类型

    IdType支持的类型有:

    描述
    AUTO数据库 ID 自增
    NONE无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
    INPUTinsert 前自行 set 主键值
    ASSIGN_ID分配 ID(主键类型为 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法)
    ASSIGN_UUID分配 UUID,主键类型为 String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认 default 方法)
    ID_WORKER分布式全局唯一 ID 长整型类型(please use ASSIGN_ID)
    UUID32 位 UUID 字符串(please use ASSIGN_UUID)
    ID_WORKER_STR分布式全局唯一 ID 字符串类型(please use ASSIGN_ID)

    这里比较常见的有三种:

    • AUTO:利用数据库的id自增长
    • INPUT:手动生成id
    • ASSIGN_ID:雪花算法生成Long类型的全局唯一id,这是默认的ID策略

    2.3.@TableField

    说明:

    描述:普通字段注解

    示例:

    @TableName("user")
    public class User {
        @TableId
        private Long id;
        private String name;
        private Integer age;
        @TableField("isMarried")
        private Boolean isMarried;
        @TableField("`concat`")
        private String concat;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    一般情况下我们并不需要给字段添加@TableField注解,一些特殊情况除外:

    • 成员变量名与数据库字段名不一致
    • 成员变量是以isXXX命名,按照JavaBean的规范,MybatisPlus识别字段时会把is去除,这就导致与数据库不符。
    • 成员变量名与数据库一致,但是与数据库的关键字冲突。使用@TableField注解给字段名添加````转义

    支持的其它属性如下:

    属性类型必填默认值描述
    valueString“”数据库字段名
    existbooleantrue是否为数据库表字段
    conditionString“”字段 where 实体查询比较条件,有值设置则按设置的值为准,没有则为默认全局的 %s=#{%s},参考(opens new window)
    updateString“”字段 update set 部分注入,例如:当在version字段上注解update=“%s+1” 表示更新时会 set version=version+1 (该属性优先级高于 el 属性)
    insertStrategyEnumFieldStrategy.DEFAULT举例:NOT_NULL
    insert into table_a(column) values (#{columnProperty})
    updateStrategyEnumFieldStrategy.DEFAULT举例:IGNORED
    update table_a set column=#{columnProperty}
    whereStrategyEnumFieldStrategy.DEFAULT举例:NOT_EMPTY
    where column=#{columnProperty}
    fillEnumFieldFill.DEFAULT字段自动填充策略
    selectbooleantrue是否进行 select 查询
    keepGlobalFormatbooleanfalse是否保持使用全局的 format 进行处理
    jdbcTypeJdbcTypeJdbcType.UNDEFINEDJDBC 类型 (该默认值不代表会按照该值生效)
    typeHandlerTypeHander
    类型处理器 (该默认值不代表会按照该值生效)
    numericScaleString“”指定小数点后保留的位数

    3.常见配置

    MybatisPlus也支持基于yaml文件的自定义配置,详见官方文档:
    使用配置 | MyBatis-Plus

    大多数的配置都有默认值,因此我们都无需配置。但还有一些是没有默认值的,例如:

    • 实体类的别名扫描包
    • 全局id类型
    mybatis-plus:
      type-aliases-package: com.itheima.mp.domain.po
      global-config:
        db-config:
          id-type: auto # 全局id类型为自增长
    
    • 1
    • 2
    • 3
    • 4
    • 5

    需要注意的是,MyBatisPlus也支持手写SQL的,而mapper文件的读取地址可以自己配置:

    mybatis-plus:
      mapper-locations: "classpath*:/mapper/**/*.xml" # Mapper.xml文件地址,当前这个是默认值。
    
    • 1
    • 2

    可以看到默认值是classpath*:/mapper/**/*.xml,也就是说我们只要把mapper.xml文件放置这个目录下就一定会被加载。

    例如,我们新建一个UserMapper.xml文件:
    image.png
    然后在其中定义一个方法:

    
    DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.itheima.mp.mapper.UserMapper">
    
        <select id="queryById" resultType="User">
            SELECT * FROM user WHERE id = #{id}
        select>
    mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    然后在测试类UserMapperTest中测试该方法:

    @Test
    void testQuery() {
        User user = userMapper.queryById(1L);
        System.out.println("user = " + user);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4.核心功能

    刚才的案例中都是以id为条件的简单CRUD,一些复杂条件的SQL语句就要用到一些更高级的功能了。

    4.1.条件构造器

    除了新增以外,修改、删除、查询的SQL语句都需要指定where条件。因此BaseMapper中提供的相关方法除了以id作为where条件以外,还支持更加复杂的where条件。
    image.png
    参数中的Wrapper就是条件构造的抽象类,其下有很多默认实现,继承关系如图:
    image.png

    Wrapper的子类AbstractWrapper提供了where中包含的所有条件构造方法:
    image.png
    而QueryWrapper在AbstractWrapper的基础上拓展了一个select方法,允许指定查询字段:
    image.png
    而UpdateWrapper在AbstractWrapper的基础上拓展了一个set方法,允许指定SQL中的SET部分:
    image.png

    接下来,我们就来看看如何利用Wrapper实现复杂查询。

    4.1.1.QueryWrapper

    无论是修改、删除、查询,都可以使用QueryWrapper来构建查询条件。接下来看一些例子:
    查询:查询出名字中带o的,存款大于等于1000元的人。代码如下:

    @Test
    void testQueryWrapper() {
        // 1.构建查询条件 where name like "%o%" AND balance >= 1000
        QueryWrapper<User> wrapper = new QueryWrapper<User>()
                .select("id", "username", "info", "balance")
                .like("username", "o")
                .ge("balance", 1000);
        // 2.查询数据
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    更新:更新用户名为jack的用户的余额为2000,代码如下:

    @Test
    void testUpdateByQueryWrapper() {
        // 1.构建查询条件 where name = "Jack"
        QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "Jack");
        // 2.更新数据,user中非null字段都会作为set语句
        User user = new User();
        user.setBalance(2000);
        userMapper.update(user, wrapper);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    4.1.2.UpdateWrapper

    基于BaseMapper中的update方法更新时只能直接赋值,对于一些复杂的需求就难以实现。
    例如:更新id为1,2,4的用户的余额,扣200,对于的SQL应该是:

    UPDATE user SET balance = balance - 200 WHERE id in (1, 2, 4)
    
    • 1

    SET的赋值结果是基于字段现有值的,这个时候就要利用UpdateWrapper中的setSql功能了:

    @Test
    void testUpdateWrapper() {
        List<Long> ids = List.of(1L, 2L, 4L);
        // 1.生成SQL
        UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
                .setSql("balance = balance - 200") // SET balance = balance - 200
                .in("id", ids); // WHERE id in (1, 2, 4)
    	// 2.更新,注意第一个参数可以给null,也就是不填更新字段和数据,
        // 而是基于UpdateWrapper中的setSQL来更新
        userMapper.update(null, wrapper);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    4.1.3.LambdaQueryWrapper

    无论是QueryWrapper还是UpdateWrapper在构造条件的时候都需要写死字段名称,会出现字符串魔法值。这在编程规范中显然是不推荐的。
    那怎么样才能不写字段名,又能知道字段名呢?

    其中一种办法是基于变量的gettter方法结合反射技术。因此我们只要将条件对应的字段的getter方法传递给MybatisPlus,它就能计算出对应的变量名了。而传递方法可以使用JDK8中的方法引用Lambda表达式。
    因此MybatisPlus又提供了一套基于Lambda的Wrapper,包含两个:

    • LambdaQueryWrapper
    • LambdaUpdateWrapper

    分别对应QueryWrapper和UpdateWrapper

    其使用方式如下:

    @Test
    void testLambdaQueryWrapper() {
        // 1.构建条件 WHERE username LIKE "%o%" AND balance >= 1000
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.lambda()
                .select(User::getId, User::getUsername, User::getInfo, User::getBalance)
                .like(User::getUsername, "o")
                .ge(User::getBalance, 1000);
        // 2.查询
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    更多构造器使用方法总结
    构造器方法

    4.2.自定义SQL

    在演示UpdateWrapper的案例中,我们在代码中编写了更新的SQL语句:
    image.png
    这种写法在某些企业也是不允许的,因为SQL语句最好都维护在持久层,而不是业务层。就当前案例来说,由于条件是in语句,只能将SQL写在Mapper.xml文件,利用foreach来生成动态SQL。
    这实在是太麻烦了。假如查询条件更复杂,动态SQL的编写也会更加复杂。

    所以,MybatisPlus提供了自定义SQL功能,可以让我们利用Wrapper生成查询条件,再结合Mapper.xml编写SQL

    4.2.1.基本用法

    以当前案例来说,我们可以这样写:

    @Test
    void testCustomWrapper() {
        // 1.准备自定义查询条件
        List<Long> ids = List.of(1L, 2L, 4L);
        QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id", ids);
    
        // 2.调用mapper的自定义方法,直接传递Wrapper
        userMapper.deductBalanceByIds(200, wrapper);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    然后在UserMapper中自定义SQL:

    public interface UserMapper extends BaseMapper<User> {
        @Select("UPDATE user SET balance = balance - #{money} ${ew.customSqlSegment}")
        void deductBalanceByIds(@Param("money") int money, @Param("ew") QueryWrapper<User> wrapper);
    }
    
    • 1
    • 2
    • 3
    • 4

    这样就省去了编写复杂查询条件的烦恼了。

    4.2.2.多表关联

    理论上来将MyBatisPlus是不支持多表查询的,不过我们可以利用Wrapper中自定义条件结合自定义SQL来实现多表查询的效果。
    例如,我们要查询出所有收货地址在北京的并且用户id在1、2、4之中的用户
    要是自己基于mybatis实现SQL,大概是这样的:

      <select id="queryUserByIdAndAddr" resultType="com.itheima.mp.domain.po.User">
          SELECT *
          FROM user u
          INNER JOIN address a ON u.id = a.user_id
          WHERE u.id
          <foreach collection="ids" separator="," item="id" open="IN (" close=")">
              #{id}
          foreach>
          AND a.city = #{city}
      select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    可以看出其中最复杂的就是WHERE条件的编写,如果业务复杂一些,这里的SQL会更变态。但是基于自定义SQL结合Wrapper的玩法,我们就可以利用Wrapper来构建查询条件,然后手写SELECT及FROM部分,实现多表查询。
    查询条件这样来构建:

    @Test
    void testCustomJoinWrapper() {
        // 1.准备自定义查询条件
        QueryWrapper<User> wrapper = new QueryWrapper<User>()
                .in("u.id", List.of(1L, 2L, 4L))
                .eq("a.city", "北京");
    
        // 2.调用mapper的自定义方法
        List<User> users = userMapper.queryUserByWrapper(wrapper);
    
        users.forEach(System.out::println);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    然后在UserMapper中自定义方法:

    @Select("SELECT u.* FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment}")
    List<User> queryUserByWrapper(@Param("ew")QueryWrapper<User> wrapper);
    
    • 1
    • 2

    当然,也可以在UserMapper.xml中写SQL:

    <select id="queryUserByIdAndAddr" resultType="com.itheima.mp.domain.po.User">
        SELECT * FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment}
    select>
    
    • 1
    • 2
    • 3

    4.3.Service接口

    MybatisPlus不仅提供了BaseMapper,还提供了通用的Service接口及默认实现,封装了一些常用的service模板方法。
    通用接口为IService,默认实现为ServiceImpl,其中封装的方法可以分为以下几类:

    • save:新增
    • remove:删除
    • update:更新
    • get:查询单个结果
    • list:查询集合结果
    • count:计数
    • page:分页查询

    4.3.1.CRUD

    我们先俩看下基本的CRUD接口。
    新增
    image.png

    • save是新增单个元素
    • saveBatch是批量新增
    • saveOrUpdate是根据id判断,如果数据存在就更新,不存在则新增
    • saveOrUpdateBatch是批量的新增或修改

    删除:
    image.png

    • removeById:根据id删除
    • removeByIds:根据id批量删除
    • removeByMap:根据Map中的键值对为条件删除
    • remove(Wrapper):根据Wrapper条件删除
    • ~~removeBatchByIds~~:暂不支持

    修改:
    image.png

    • updateById:根据id修改
    • update(Wrapper):根据UpdateWrapper修改,Wrapper中包含setwhere部分
    • update(T,Wrapper):按照T内的数据修改与Wrapper匹配到的数据
    • updateBatchById:根据id批量修改

    Get:
    image.png

    • getById:根据id查询1条数据
    • getOne(Wrapper):根据Wrapper查询1条数据
    • getBaseMapper:获取Service内的BaseMapper实现,某些时候需要直接调用Mapper内的自定义SQL时可以用这个方法获取到Mapper

    List:
    image.png

    • listByIds:根据id批量查询
    • list(Wrapper):根据Wrapper条件查询多条数据
    • list():查询所有

    Count
    image.png

    • count():统计所有数量
    • count(Wrapper):统计符合Wrapper条件的数据数量

    getBaseMapper
    当我们在service中要调用Mapper中自定义SQL时,就必须获取service对应的Mapper,就可以通过这个方法:
    image.png

    4.3.2.基本用法

    由于Service中经常需要定义与业务有关的自定义方法,因此我们不能直接使用IService,而是自定义Service接口,然后继承IService以拓展方法。同时,让自定义的Service实现类继承ServiceImpl,这样就不用自己实现IService中的接口了。

    首先,定义UserService,继承IService

    package com.itheima.mp.service;
    
    import com.baomidou.mybatisplus.extension.service.IService;
    import com.itheima.mp.domain.po.User;
    
    public interface UserService extends IService<User> {
        // 拓展自定义方法
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    然后,编写UserServiceImpl类,继承ServiceImpl,实现UserService

    package com.itheima.mp.service.impl;
    
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.itheima.mp.domain.po.User;
    import com.itheima.mp.domain.po.service.UserService;
    import com.itheima.mp.mapper.UserMapper;
    import org.springframework.stereotype.Service;
    
    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    项目结构如下:
    image.png

    最后,编写一个测试类,测试一下:

    package com.itheima.mp.service;
    
    import com.itheima.mp.domain.po.User;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    import java.util.List;
    
    import static org.junit.jupiter.api.Assertions.*;
    
    @SpringBootTest
    class UserServiceTest {
    
        @Autowired
        UserService userService;
    
        @Test
        void testService() {
            List<User> list = userService.list();
            list.forEach(System.out::println);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    4.3.3.Lambda

    Service中对LambdaQueryWrapperLambdaUpdateWrapper的用法进一步做了简化。我们无需自己通过new的方式来创建Wrapper,而是直接调用lambdaQuerylambdaUpdate方法:

    基于Lambda查询:

    @Test
    void testLambdaQuery() {
        // 1.查询1个
        User rose = userService.lambdaQuery()
                .eq(User::getUsername, "Rose")
                .one(); // .one()查询1个
        System.out.println("rose = " + rose);
    
        // 2.查询多个
        List<User> users = userService.lambdaQuery()
                .like(User::getUsername, "o")
                .list(); // .list()查询集合
        users.forEach(System.out::println);
    
        // 3.count统计
        Long count = userService.lambdaQuery()
                .like(User::getUsername, "o")
                .count(); // .count()则计数
        System.out.println("count = " + count);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    可以发现lambdaQuery方法中除了可以构建条件,而且根据链式编程的最后一个方法来判断最终的返回结果,可选的方法有:

    • .one():最多1个结果
    • .list():返回集合结果
    • .count():返回计数结果

    lambdaQuery还支持动态条件查询。比如下面这个需求:

    定义一个方法,接收参数为username、status、minBalance、maxBalance,参数可以为空。

    • 如果username参数不为空,则采用模糊查询;
    • 如果status参数不为空,则采用精确匹配;
    • 如果minBalance参数不为空,则余额必须大于minBalance
    • 如果maxBalance参数不为空,则余额必须小于maxBalance

    这个需求就是典型的动态查询,在业务开发中经常碰到,实现如下:

    @Test
    void testQueryUser() {
        List<User> users = queryUser("o", 1, null, null);
        users.forEach(System.out::println);
    }
    
    public List<User> queryUser(String username, Integer status, Integer minBalance, Integer maxBalance) {
        return userService.lambdaQuery()
                .like(username != null , User::getUsername, username)
                .eq(status != null, User::getStatus, status)
                .ge(minBalance != null, User::getBalance, minBalance)
                .le(maxBalance != null, User::getBalance, maxBalance)
                .list();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    基于Lambda更新:

    @Test
    void testLambdaUpdate() {
        userService.lambdaUpdate()
                .set(User::getBalance, 800) // set balance = 800
                .eq(User::getUsername, "Jack") // where username = "Jack"
                .update(); // 执行Update
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    lambdaUpdate()方法后基于链式编程,可以添加set条件和where条件。但最后一定要跟上update(),否则语句不会执行。

    lambdaUpdate()同样支持动态条件,例如下面的需求:

    基于IService中的lambdaUpdate()方法实现一个更新方法,满足下列需求:

    • 参数为balance、id、username
    • id或username至少一个不为空,根据id或username精确匹配用户
    • 将匹配到的用户余额修改为balance
    • 如果balance为0,则将用户status修改为冻结状态

    实现如下:

    @Test
    void testUpdateBalance() {
        updateBalance(0L, 1L, null);
    }
    
    public void updateBalance(Long balance, Long id, String username){
        userService.lambdaUpdate()
                .set(User::getBalance, balance)
                .set(balance == 0, User::getStatus, 2)
                .eq(id != null, User::getId, id)
                .eq(username != null, User::getId, username)
                .update();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    4.4.静态工具

    有的时候Service之间也会相互调用,为了避免出现循环依赖问题,MybatisPlus提供一个静态工具类:Db,其中的一些静态方法与IService中方法签名基本一致,也可以帮助我们实现CRUD功能:
    image.png

    示例:

    @Test
    void testDbGet() {
        User user = Db.getById(1L, User.class);
        System.out.println(user);
    }
    
    @Test
    void testDbList() {
        // 利用Db实现复杂条件查询
        List<User> list = Db.lambdaQuery(User.class)
                .like(User::getUsername, "o")
                .ge(User::getBalance, 1000)
                .list();
        list.forEach(System.out::println);
    }
    
    @Test
    void testDbUpdate() {
        Db.lambdaUpdate(User.class)
                .set(User::getBalance, 2000)
                .eq(User::getUsername, "Rose");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
  • 相关阅读:
    goroutine摘要
    怎么关闭管理员权限?
    uniapp 原生sqlite本地数据库管理 Ba-Sqlite
    二叉树基本性质+oj题解析
    中科驭数DPU技术开放日秀“肌肉”:云原生网络、RDMA、安全加速、低延时网络等方案组团亮相
    氨丙基表面修饰二氧化硅亚微米微球/二氧化硅微球表面负载硫化亚铁纳米晶
    Using Multiple RDF Knowledge Graphs for Enriching ChatGPT Responses
    2.1 JAVA基础语法
    在 Python 中打印二叉树
    haproxy实验
  • 原文地址:https://blog.csdn.net/m0_73886108/article/details/132920021