Mybatis-Plus初级篇

Mybatis-Plus初级篇

1、简单介绍

最近项目中开始使用mybatis-plus了,我体验了一下,觉得很爽,连SQL语句都不需要来写了。当然如果有需要还是可以自己来写的

当然mybatis-plus还提供了更好的使用方式。

参考官方:

MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
  • 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

下面将会系统全面介绍这里的使用方式

2、快速使用

从上面的特性中可以看到强大的CRUD功能,那么先来快速搭建一下:

2.1、准备数据库表

从官方获取得到表结构:

DROP TABLE IF EXISTS user;

CREATE TABLE user
(
    id BIGINT(20) NOT NULL COMMENT '主键ID',
    name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
    age INT(11) NULL DEFAULT NULL COMMENT '年龄',
    email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
    PRIMARY KEY (id)
);


DELETE FROM user;

INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');

2.2、快速构建项目

因为Mybatis-Plus属于第三方依赖,所以springboot并未将其整合进来。所以需要自己去引入mybatis-plus的启动依赖以及MySQL的连接器。我的依赖配置

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.4.1</spring-boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

那么接下来配置一下properties.properties

# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name=defaultDataSource
# 数据库连接地址  &rewriteBatchedStatements=true  115005  21000   大概是五倍左右的效率 
# https://blog.csdn.net/liuwei0376/article/details/106568988/
spring.datasource.url=jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=UTC
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=root
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹:

@SpringBootApplication
@MapperScan("com.example.demo.mapper")
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

创建对应的实体类:

@Data
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

创建mapper:

public interface UserMapper extends BaseMapper<User> {

}

2.3、测试

先用单元测试来进行操作:

@SpringBootTest
public class SampleTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelect() {
        System.out.println(("----- selectAll method test ------"));
        List<User> userList = userMapper.selectList(null);
        Assert.assertEquals(5, userList.size());
        userList.forEach(System.out::println);
    }

}

查看浏览器输出信息:

User(id=1, name=Jone, age=18, email=test1@baomidou.com)
User(id=2, name=Jack, age=20, email=test2@baomidou.com)
User(id=3, name=Tom, age=28, email=test3@baomidou.com)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
User(id=5, name=Billie, age=24, email=test5@baomidou.com)

3、Wrapper

首先介绍一下条件构造器,首先介绍一下Wrapper的体系结构:

通过上面的观察,可以看到有普通的wrapper来使用,也有链式的wrapper来进行使用。

mybatis-plus提供的wrapper被称之为条件构造器,那么通过名字就非常容易来进行理解。

所谓的条件是什么?我之前在MySQL中写过一篇文件来介绍了一下select语句的执行顺序。

insert into table () values ();select * from table where condition.......update table set k1=v1 where conditiondelete from table where condition    

对于条件表达式来说,对于insert来说,是用不上的。

而对于select、update、delete来说,这些非常有价值。都需要利用条件来补充SQL语句

但是从上面可以看到wrapper的分类,分为了两种类型。QueryWrapper和UpdateWrapper

QueryWrapper是针对于select语句的;而UpdateWrapper是针对于update

最常见的就是

QueryWrapper:提供了selectUpdateWrapper:提供了set   

稍后会在使用的时候提出来。

那么参考一下官方文档提供的用法:

https://baomidou.com/pages/10c804/#abstractwrapper

比较简单,使用的话可以自行来进行参考。然后一会儿在进行使用的时候可以来看一下对应的使用。

3.1、测试使用Mapper

下面将会把BaseMapper中的所有方法都会测试一遍,然后总结一下自己的理解。

3.1.1、insert

    @Test    public void testinsert() {        User user = new User();        user.setName("xuan");        user.setAge(22);        user.setEmail("xuan@qq.com");        int insert = this.userMapper.insert(user);        System.out.println(insert);        System.out.println(user);    }

我觉得测试这些方法最好的方式就是首先想要测试什么,然后先写出来对应的SQL语句。

对于插入来说,最常用的莫过于是下面的这种使用方式:

insert into table (k1,k2,k3) values (v1,v2,v3);

那么对于上面的java代码来说,User类中的属性就是k,而set方法设置进去的值就是要插入进去的值。

这里需要注意一点的是,set了哪些值,哪些值就会值。没有进行set的值,默认使用的是数据库中的默认值。

这句话还是通过SQL语句来看:

INSERT INTO user ( id, name, email ) VALUES ( ?, ?, ? )

可以看到我只设置了id,name,email属性,那么这里在插入的时候就只有这三个属性。

3.1.2、deleteById

    /**     * delete from table where id = x;     */    @Test    public void testdeleteById() {        int i = this.userMapper.deleteById(1470409869745864705L);        System.out.println(i);    }

这个就不说了。

3.1.3、deleteByMap

从方法名称来看,通过Map来进行删除。对于一条delete语句来说,是非常固定的。

delete from table where id = x and... or ....;

那么map中的key和value已经是非常明显了。默认的是等值连接

key就是字段,value就是对应的名称。

    @Test    public void testdeleteByMap() {        Map<String, Object> map = new HashMap<>();        map.put("name", "saveBatch12");        map.put("email", "saveBatch1@qq.com");        int i = this.userMapper.deleteByMap(map);        System.out.println(i);    }

对应的SQL:

DELETE FROM user WHERE name = ? AND email = ?

3.2.3、delete

看一下方法功能描述:

    /**     * 根据 entity 条件,删除记录     *     * @param queryWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)     */    int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

根据entity条件来删除记录。那么entity是什么?看看对queryWrapper的解释:

实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句),那么就意味着利用entity中的字段来构建where中的条件。

测试:

    @Test    public void testdelete1() {        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();        userQueryWrapper.eq("name", "xuan").eq("email", "xuan@qq.com");        int delete = this.userMapper.delete(userQueryWrapper);        System.out.println(delete);    }

对应的SQL:

DELETE FROM user WHERE name = ? AND email = ?

3.2.4、deleteBatchIds

这个根据id来批量进行删除,也没有什么好讲的。

@Testpublic void testdeleteBatchIds() {    List<Long> idList = Arrays.asList(1470023623290839042L, 1470023675442843649L, 1470023713854210050L);    int i = this.userMapper.deleteBatchIds(idList);    System.out.println("影响的行的行数");}

注意必须是要有值的。

3.2.5、updateById

@Testpublic void testupdateById() {    User user = new User();    user.setId(3L);    user.setEmail("1234555");    int i = this.userMapper.updateById(user);    System.out.println(i);}

这里根据主键来进行删除,主键是要有的,不然无法来进行更新。

对象里面设置了什么值,就会来修改什么值。而其他的值不会发生改变。类似mybatis中的selective方法

3.2.6、update

这个和上面的相比是类似的。

    @Test
    public void testupdateWrapper() {
        UpdateWrapper<User> userQueryWrapper = new UpdateWrapper<>();
        userQueryWrapper.eq("age", 22).eq("name", "Jack");
        User user = new User();
        user.setName("Helo");
        int update = this.userMapper.update(user, userQueryWrapper);
        System.out.println(update);
    }

将user作为要更新的值,将wrapper作为要来进行匹配的值。但是这里的user不能够为空,否则update中的set就没有对应的值,就会报错。

对应的SQL就是:

UPDATE user SET name=? WHERE (age = ? AND name = ?)

对于要进行set的字段就是对象中的属性的值,而where就是条件构造器所来构造的表达式了。

还需要注意一点,对于wrapper来说,这里面的无论是QueryWrapper还是UpdateWrapper来说,都是可以来使用的。因为二者都继承了Wrapper,都有其共同使用的方式。

这里还有一个重载方法,这里记录一下,因为曾经在使用的时候犯下了一个错误。

    @Test
    public void testupdateWrapper() {
       UpdateWrapper<User> userQueryWrapper = new update.UpdateWrapper<>();
        userQueryWrapper.eq("age", 22).eq("name", "Cat");
        int update = this.userMapper.update(null, userQueryWrapper);
        System.out.println(update);
    }

上面的单元测试会报错,所以导致我一直以为这里不能够放置null参数。但是偶然间看到项目中有人使用了这个方法,而且方法参数上放置的就是null和wrapper。所以这里我去查了一下资料。

记录一下这里的问题。这里是可以放置null的,但是有个前提:

    @Test    public void testupdateWrapper() {        UpdateWrapper<User> userQueryWrapper = new update.UpdateWrapper<>();        userQueryWrapper.eq("age", 22).eq("name", "Cat").set("age",29);        int update = this.userMapper.update(null, userQueryWrapper);        System.out.println(update);    }

看到上面的UpdateWrapper在进行使用的时候使用了一个set属性!!!这样就可以来进行更新值了

那么这种场景的使用范围很广!为什么?因为有时候在进行更新的时候,有可能说只来更新一个字段或者是少量字段,那么没有必要来创建一个对象,专门来设置一个属性放入到参数中去。直接在wrapper后面进行追加即可。

感觉这种比较方便来进行使用,这个在后面的lambda构造器中更加方法的来进行使用。

3.2.7、selectById

    @Test    public void testselectById() {        User user = this.userMapper.selectById(1470025846955642882L);        System.out.println(user);    }

3.2.8、selectBatchIds

    @Test    public void testselectBatchIds() {        List<Long> idList = Arrays.asList(1L, 2L, 3L);        // 如果为空,那么直接报错        List<User> users = this.userMapper.selectBatchIds(idList);        users.forEach(System.out::println);    }

3.2.9、selectByMap

    @Test    public void testselectByMap() {        Map<String, Object> map = new HashMap<>();        map.put("name", "xuan");        map.put("email", "xuan@qq.com");        // 如果为空,那么查询所有的值        List<User> users = this.userMapper.selectByMap(null);        users.forEach(System.out::println);    }

通过对应的SQL就知道构造的条件表达式是什么意思。很简单

3.2.10、selectOne

@Testpublic void testselectOne() {    UpdateWrapper<User> userQueryWrapper = new UpdateWrapper<>();    userQueryWrapper.eq("age", 20).eq("name", "Jack");    User user = this.userMapper.selectOne(userQueryWrapper);    System.out.println(user);}

这个用起来要注意一下,除非保证查询出来的记录是只有一条的。如果有多条,那么会直接进行报错。如果想要看的话,那么可以看一下升级版本的。

3.2.11、selectCount

查询记录条数

    @Test    public void testselectCount() {        UpdateWrapper<User> userQueryWrapper = new UpdateWrapper<>();        userQueryWrapper.eq("age", 20).eq("name", "Jack");        Integer integer = this.userMapper.selectCount(userQueryWrapper);        System.out.println(integer);    }

这里在没有提及到要去查询哪些字段,那么默认查询的就是整条记录。也即是:

select count(*) from user;

那么来一个有提及到查询的:

    @Test    public void testselectCount() {         QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();        userQueryWrapper.eq("age", 20).eq("name", "Jack").select("age","name");        Integer integer = this.userMapper.selectCount(userQueryWrapper);        System.out.println(integer);    }

注意一下这里的选择查询的哪两个字段,但是这样子来写是不可以的。对应的SQL如下所以

SELECT COUNT(  age,name  ) FROM user     WHERE (age = ? AND name = ?)

报错信息。

但是这里还有一种情况,就是查询一个字段的时候

    @Test    public void testselectCount() {         QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();        userQueryWrapper.eq("age", 20).eq("name", "Jack").select("age");        Integer integer = this.userMapper.selectCount(userQueryWrapper);        System.out.println(integer);    }

假如说只是来查询对应的age字段所对应的值,那么这个时候就可能面临着一个问题:

age字段对应的值如果有存在null的情况,那么这里肯定会来过滤掉的。如果没有,那么就会是全量匹配。

如果是查询记录,那么是查询全部的记录;如果是查询字段的记录条数,会过滤掉对应字段为null的值;

也就是说:

count(*)>=count(column)

3.2.12、selectList

将查询出来的数据封装成list集合。这个是最简单的,也是一开始就来使用的。

    @Test    public void testselectList() {        List<User> users = this.userMapper.selectList(null);        users.forEach(System.out::println);    }    @Test    public void testselectListTwo() {        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();        userQueryWrapper.select("age","name").eq("age", 22).eq("name", "Helo");        List<User> users = this.userMapper.selectList(userQueryWrapper);        users.forEach(System.out::println);    }

第一个查询,因为条件构造器中没有条件,那么也就相当于是一个null值,所以没有条件,那么就去查询全部;

第二个因为有条件,会将wrapper中的条件来组装成where中的条件构造表达式;但是条件中要求只需要查询出来age和name字段的值,那么因为其他的值没有查询,为默认值,所以mybatis-plus在进行封装的时候,就会封装成null。

3.2.13、selectMaps

将查询出来的结果封装成maps

    @Test    public void testselectMaps() {        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();        userQueryWrapper.select("age","name").eq("age", 22).eq("name", "Cat");        List<Map<String, Object>> maps = this.userMapper.selectMaps(userQueryWrapper);        maps.forEach(System.out::println);    }

因为这里只会查询出来两个字段的值。首先看一下对应的效果

{name=Cat, age=22}{name=Cat, age=22}{name=Cat, age=22}

因为一行记录会被封装成一个Map,那么这里匹配到了三行记录,所以这里会有三条map数据。

其中,对于里面的一个map来说,key是数据库中的属性,value是对应的值。这种也有对应的需要。

3.2.13、selectObjs

这种方法也是比较好用的,用来查询第一列的所有值

    @Test    public void testselectObjs() {        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();        userQueryWrapper.select("name","age").eq("age", 22).eq("name", "Helo");        List<Object> selectObjs = this.userMapper.selectObjs(userQueryWrapper);        System.out.println(selectObjs);    }

尽管这里写了查询出两列(name和age),但是显示的时候,只会显示出来第一列的值,哪个字段在前面就会显示出来哪个字段的值。

3.2.14、selectPage

分页查询出来对应的列表,这个是最常用的。

    @Test    public void testselectPage() {        Page<User> page = new Page<>(10, 20);        //  Page<User> page = new Page<>(10, 20,false);        // 执行分页查询。后面的wrapper是条件,查询指定条件的数据。        Page<User> userPage = userMapper.selectPage(page, null);        long total = userPage.getTotal();        long size = userPage.getSize();        List<User> records = userPage.getRecords();        System.out.println("totel---" + total + "    ,size----->" + size + "     records------" + records + "      recordsize" + records.size());        //  System.out.println(userPage);    }

只不过这里需要注意一点的是在Page的构造函数中,有一个构造重载。原因是因为执行上面的SQL语句的时候,首先回去查询所有的count,然后再会去查询当前页中显示的数据。

第一次SQL语句:SELECT COUNT(*) FROM user第二次SQL语句:SELECT id,name,age,email FROM user LIMIT ?,?

而如果将Page的构造中设置一个fasle,那么第一条SQL语句将不会再来执行,只是会查询到当前页的数据。

上面没有来使用wrapper,如果使用wrapper,那么可以来进行搜索功能,将指定的匹配的数据展示在页面上来进行显示。

    @Test
    public void testselectPageWrapper() {
        Page<User> page = new Page<>(1, 20);
        LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery().likeRight(User::getEmail, "123");
        Page<User> userPage = userMapper.selectPage(page, wrapper);
        long total = userPage.getTotal();
        long size = userPage.getSize();
        // 得到对应的结果记录
        List<User> records = userPage.getRecords();
    }

这个也是非常有用的。前端需要根据条件来进行搜索,搜索完成之后,将数据回显到页面上。

3.2.15、selectMapsPage

将查询出来的封装到list中去,而每个map是其中的一个一条记录:

    @Test
    public void selectMapsPage() {
        QueryWrapper<User> wrapper = new QueryWrapper<User>();
        // wrapper.like("name", "王");
        int pageNum = 1; // 当前页
        int pageSize = 3; // 每页大小
        //  Page<User> 修改为 IPage<Map<String, Object>>
        // Page<User> page = new Page<>(pageNum, pageSize); // selectMapsPage会报错
        IPage<Map<String, Object>> page = new Page<>(pageNum, pageSize);
        userMapper.selectMapsPage(page, wrapper);
        System.out.println("总页数:" + page.getPages());
        System.out.println("总记录数:" + page.getTotal());
        List<Map<String, Object>> list = page.getRecords();
        list.forEach(System.out::println);
    }

3.2、测试使用service

关于service中的调用,其实只要搞懂了mapper中的方法,那么学习使用service的就很简单了。

其实在service中的无非是增加了几个lambda表达式来构建wrapper的方式来进行使用。

下面来看一下service中实现方式:

首先新建一个接口:

public interface UserService extends IService<User> {}

查看这个接口,继承了IService这个接口,这个接口是父类中提供的。那么可以看一下IService这个接口中的方法,可以看到有很多的默认方法(JDK8中新增的方法)。但是通过查看方法可以发现,这里面有存在着大量的批处理方法。

这里我需要提醒一下,mybatis-plus中的批处理方法实则不咋地!我测试了插入80W条数据到数据库中去,耗时时间长达N秒。

所以还需要额外的提供方式。当然这个也会在后面来细说。

那么先列举一下IService接口中mapper中没有的方法,其中简单的一笔带过

3.2.1、save

调用mapper的insert方法

3.2.2、removeById

调用mapper的removeById方法

3.2.3、removeByMap

调用mapper的deleteByMap方法

3.2.4、remove

调用mapper的delete方法

3.2.5、removeByIds

调用mapper的deleteBatchIds方法

3.2.6、updateById

调用mapper的updateById方法

3.2.1、update

调用mapper的update方法。但是一直没有想明白一个问题。就是源码中为什么会这么来写:

    default boolean update(Wrapper<T> updateWrapper) {        return update(null, updateWrapper);    }    /**     * 根据 whereEntity 条件,更新记录     *     * @param entity        实体对象     * @param updateWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}     */    default boolean update(T entity, Wrapper<T> updateWrapper) {        return SqlHelper.retBool(getBaseMapper().update(entity, updateWrapper));    }

传递的entity为空,那么调用重载方法的时候,需要调用mapper来进行调用,难道这样子不会出现问题?

刚刚试过了,如果mapper调用update方法来进行操作,entity为空的时候,那么会抛出异常。

原因是SQL语句长下面这样子

SQL: UPDATE user        WHERE (age = ? AND name = ?)

3.2.7、updateBatchById

查看源码,看起来很简单。

3.2.8、saveOrUpdate

向数据库中插入或者是更新一条记录。这个方法我感觉有点鸡肋

首先是否有id,如果没有id,那么就直接插入;如果有id,那么执行更新操作。

与其类似的就是saveOrUpdatebatch方法了。

3.2.9、getById

调用mapper的selectById方法

3.2.10、listByIds

调用mapper的selectBatchIds方法

3.2.11、listByMap

调用mapper的selectByMap方法

3.2.12、getOne

调用mapper的getOne方法。但是这里有一点区别:

    default T getOne(Wrapper<T> queryWrapper) {        return getOne(queryWrapper, true);    }

如果只是一条数据,那么将不会跑出来对应异常。

如果查询出来多条数据,那么这里将会抛出异常;但是这种是可以来进行处理的,因为有一个参数可以来进行设置,如果是false,查询出来多条数据的话,只会返回第一条数据来进行显示。

3.2.1、selectByMap

调用mapper的selectByMap方法

...

找了方法找了半天,发现service中的方法中都有mapper中的影子。如果是单表操作,那么以后在service中直接引入别的service进来进行操作即可。当然也适用于多表进行联查的情况,因为如果是多表进行联合查询,那么获取得到对应的service来调用方法也是可以的。

也许,有了mp之后,我们就很少来写SQL语句。哪怕是多表联合查询的情况,只不过是多查询几次而已。

比如说A、A-B、B三张表来进行联合查询。我现在想要通过A来获取得到B表中的信息(当然中间表A-B是没有的),那么通过A可以获取得到中间表的信息,然后在A-B中来获取的A中对应的信息,再通过A-B中的信息获取得到B的信息。

感觉像是在使用Feign进行远程调用。

这样子操作也是可行的,但是IO次数过于频繁,性能上会有影响。所以不推荐来进行使用。所以针对于多表联查的情况,还是推荐自己来编写SQL。

那么看一下没有的方法:

default List<Map<String, Object>> listMaps() {
    return listMaps(Wrappers.emptyWrapper());
}

通过这里,可以看到使用工具类来获取得到wrapper的一种方法。

不过通过这个方法可以看到因为where条件是为空的,那么SQL语句最终也会没有拼接条件,所以查询的是所有的。

4、ChainWrapper

和标题3中的有些不同,从字面上看这里就多了一个Chain。那么和普通的wrapper有什么区别?

区别就是普通的wrapper将条件来进行拼装后,还需要通过mapper来进行调用;而chainwrapper在常用的几个方法中可以直接将结果查询出来或者是进行修改、删除。因为什么?因为charwrapper将mapper封装到起来了,其实底层还是在使用mapper来进行操作。

所以简单看一下ChainWrapper中的结构

public interface ChainWrapper<T> {

    /**
     * 获取 BaseMapper
     *
     * @return BaseMapper
     */
    BaseMapper<T> getBaseMapper();

    /**
     * 获取最终拿去执行的 Wrapper
     *
     * @return Wrapper
     */
    Wrapper<T> getWrapper();
}

4.1、 普通ChainWrapper和LambdaChainWrapper

看一下官方文档中的源码解释,其中里面还提供了一个案例来进行使用

/**
     * 以下的方法使用介绍:
     *
     * 一. 名称介绍
     * 1. 方法名带有 query 的为对数据的查询操作, 方法名带有 update 的为对数据的修改操作
     * 2. 方法名带有 lambda 的为内部方法入参 column 支持函数式的
     * 二. 支持介绍
     *
     * 1. 方法名带有 query 的支持以 {@link ChainQuery} 内部的方法名结尾进行数据查询操作
     * 2. 方法名带有 update 的支持以 {@link ChainUpdate} 内部的方法名为结尾进行数据修改操作
     *
     * 三. 使用示例,只用不带 lambda 的方法各展示一个例子,其他类推
     * 1. 根据条件获取一条数据: `query().eq("column", value).one()`
     * 2. 根据条件删除一条数据: `update().eq("column", value).remove()`
     *
     */

    /**
     * 链式查询 普通
     *
     * @return QueryWrapper 的包装类
     */
    default QueryChainWrapper<T> query() {
        return ChainWrappers.queryChain(getBaseMapper());
    }

    /**
     * 链式查询 lambda 式
     * <p>注意:不支持 Kotlin </p>
     *
     * @return LambdaQueryWrapper 的包装类
     */
    default LambdaQueryChainWrapper<T> lambdaQuery() {
        return ChainWrappers.lambdaQueryChain(getBaseMapper());
    }

    /**
     * 链式更改 普通
     *
     * @return UpdateWrapper 的包装类
     */
    default UpdateChainWrapper<T> update() {
        return ChainWrappers.updateChain(getBaseMapper());
    }

    /**
     * 链式更改 lambda 式
     * <p>注意:不支持 Kotlin </p>
     *
     * @return LambdaUpdateWrapper 的包装类
     */
    default LambdaUpdateChainWrapper<T> lambdaUpdate() {
        return ChainWrappers.lambdaUpdateChain(getBaseMapper());
    }

从官方源码中的注释中总结出来两点:

1、带有lambda的可以支持函数式编程(可以直接使用方法引用来操作数据库中的字段),而不带有的则支持普通的操作(所谓普通的操作就是column自己手动写数据库中对应的字段)。

2、带有Chain的可以使用使用方法来操作,也就是所谓的一链到底;不关是普通的Chain还是lambdaChain都可以来进行结果操作;

3、这里只是提供了query和update,说明只是针对于查询和修改(修改和删除操作),但是没有提供插入操作

下面来通过官方提供的案例来进行解释说明,只用不带 lambda 的方法各展示一个例子

`query().eq("column", value).one()`  
`update().eq("column", value).remove()`

1、使用不带lambda的wrapper,在对条件进行拼接的时候,需要来使用字符串来进行指定列名,表示的是对数据库中的字段来进行设置的;而带有lambda的可以直接进行方法引用来代替对应的操作。

List<User> userList = userService.lambdaQuery().eq(User::getName, "Cat").eq(User::getAge, 22).list();
List<User> list = userQueryChainWrapper.eq("name", "Cat").eq("age", 22).list();

对比一下上述的方法操作。不需要关注数据库中的列名,因为在我们的dao中已经配置好了java属性和表中列名的映射。我们操作java就相当于来操作数据库中表的列名。

2、所谓的一链到底就是直接将结果来进行返回。而不需要在通过mapper来进行查询了。比如说上面的两个操作one()和remove()操作。一个是查询,一个是删除

注意:需要根据情况来进行选择。如果需要重复使用到wrapper来进行使用,那么推荐使用下面这种方式:

LambdaQueryWrapper<ReviewContent> lambdaQueryWrapper = Wrappers<ReviewContent>lambdaQuery().eq(ReviewContent::getEleId, 11682);

推荐使用Wrappers来构建条件来达到复用的效果。

下面来看一下例子:

    @Override
    public void testTwo() {
        logger.info("当前类对应的实现类是-->>>{}", this);
         // 妈的!发现这种使用有问题!但是不知道问题出现在哪里!!!
        // LambdaQueryChainWrapper<ReviewContent> queryChainWrapper = lambdaQuery().eq(ReviewContent::getEleId, 11682);
        // 利用这种方式可以达到复用的效果
        LambdaQueryWrapper<ReviewContent> lambdaQueryWrapper = Wrappers.<ReviewContent>lambdaQuery().eq(ReviewContent::getEleId, 11682);
        List<ReviewContent> reviewContentList = this.list(lambdaQueryWrapper);
        logger.info("查询出来的结果大小是:{}", reviewContentList.size());
        // 需要注意的是这里的SQL语句:SELECT id,task_sn,ele_id,parent_ele_id,status,content,supplement FROM     		// review_content WHERE (ele_id = ? AND ele_id = ?)
        // 可以看到如果是相同的条件,但是想要不同的值,那么这里在使用的时候要小心注意
        // lambdaQueryWrapper = lambdaQueryWrapper.eq(ReviewContent::getEleId, 11683);
        // 应该先来进行清除操作,然后再进行联合查询,如果想要在上面的基础之上来进行查询,那么这种使用方式也是可行的
        lambdaQueryWrapper.clear();
        lambdaQueryWrapper = lambdaQueryWrapper.eq(ReviewContent::getEleId, 11683);
        reviewContentList = this.list(lambdaQueryWrapper);
        logger.info("修改成11683后对应的size是:{}",reviewContentList.size());
    }

从上面的案例中,可以看到如果重复使用到wrapper,那么应该注意到,是否是相同的条件俩进行修改。如果是,那么需要先进行clear操作之后,然后再将条件来进行封装。

4.2、ChainQuery

那么看看接口中提供了哪些方法:

public interface ChainQuery<T> extends ChainWrapper<T> {

    /**
     * 获取集合
     *
     * @return 集合
     */
    default List<T> list() {
        return getBaseMapper().selectList(getWrapper());
    }

    /**
     * 获取单个
     *
     * @return 单个
     */
    default T one() {
        return getBaseMapper().selectOne(getWrapper());
    }

    /**
     * 获取单个
     *
     * @return 单个
     * @since 3.3.0
     */
    default Optional<T> oneOpt() {
        return Optional.ofNullable(one());
    }

    /**
     * 获取 count
     *
     * @return count
     */
    default Integer count() {
        return SqlHelper.retCount(getBaseMapper().selectCount(getWrapper()));
    }

    /**
     * 获取分页数据
     *
     * @param page 分页条件
     * @return 分页数据
     */
    default <E extends IPage<T>> E page(E page) {
        return getBaseMapper().selectPage(page, getWrapper());
    }
}

总结出来一下,这里主要提供了两个方法。list()和one()方法,一个是查询得到集合,一个是获取得到一条数据。

分页查询方法只是传递了一个Page,那么这里默认的提供了是查询所有的记录的一种方式。而如果想要提供一种自定义查询,也是可以的,因为我们正是利用子类lambda表达式来进行操作。

4.3、ChainUpdate

再看看另外一个接口中提供的方法:

public interface ChainUpdate<T> extends ChainWrapper<T> {

    /**
     * 更新数据
     *
     * @return 是否成功
     */
    default boolean update() {
        return update(null);
    }

    /**
     * 更新数据
     *
     * @param entity 实体类
     * @return 是否成功
     */
    default boolean update(T entity) {
        return SqlHelper.retBool(getBaseMapper().update(entity, getWrapper()));
    }

    /**
     * 删除数据
     *
     * @return 是否成功
     */
    default boolean remove() {
        return SqlHelper.retBool(getBaseMapper().delete(getWrapper()));
    }
}

这里提供了三个方法。两个重载方法Update,另外一个是对delete方法来提供的。

这里沿用了Mybatis中的操作,将对数据库的操作分成了两种,一种是query,另外一种是针对增删改的。

和query一样,这里也可以一链到底,非常丝滑。

说明了这里有两种使用方式:

1、直接使用service来进行调用,最终返回得到对应结果集(list或者是单个值)。我觉得这里已经够用了,如果只是需要返回这两个,那么已经足够了;

2、也可以使用wrapper来构建条件构造器,使用lambda来进行构造条件,减少了我们去手写数据库表中的字段了,也就是手动的来写数据库中对应的字段。

对比一下:

userQueryWrapper.eq("age", 22).eq("name", "Cat")
userService.lambdaQuery().eq(User::getName, "Cat").eq(User::getAge, 22)

在研究lambdaQuery的时候,查看继承体系的时候,发现了两个接口ChainQuery和ChainWrapper

public interface ChainWrapper<T> {

    /**
     * 获取 BaseMapper
     *
     * @return BaseMapper
     */
    BaseMapper<T> getBaseMapper();

    /**
     * 获取最终拿去执行的 Wrapper
     *
     * @return Wrapper
     */
    Wrapper<T> getWrapper();
}

根据上面的内容应该知道,Wrapper有两个重要的子类QueryWraper和UpdateWraper。那么对于ChainWrapper来说,应该也有两个重要子类ChainQueryWrapper或者是ChainUpdateWrapper。那么看一下是否存在:

看一下子类:

public interface ChainQuery<T> extends ChainWrapper<T> {

    /**
     * 获取集合
     *
     * @return 集合
     */
    default List<T> list() {
        return getBaseMapper().selectList(getWrapper());
    }

    /**
     * 获取单个
     *
     * @return 单个
     */
    default T one() {
        return getBaseMapper().selectOne(getWrapper());
    }

    /**
     * 获取单个
     *
     * @return 单个
     * @since 3.3.0
     */
    default Optional<T> oneOpt() {
        return Optional.ofNullable(one());
    }

    /**
     * 获取 count
     *
     * @return count
     */
    default Integer count() {
        return SqlHelper.retCount(getBaseMapper().selectCount(getWrapper()));
    }

    /**
     * 获取分页数据
     *
     * @param page 分页条件
     * @return 分页数据
     */
    default <E extends IPage<T>> E page(E page) {
        return getBaseMapper().selectPage(page, getWrapper());
    }
}

那么有了这个lambda利器,就可以开始直接上手来进行使用了。首先看一个案例:

    @Test
    public void testLambdaInsert(){
        BaseMapper<User> baseMapper = userService.getBaseMapper();
        System.out.println(baseMapper);
        System.out.println(userMapper);
        System.out.println(baseMapper==userMapper);  //true
    }
com.baomidou.mybatisplus.core.override.MybatisMapperProxy@2e17a321
com.baomidou.mybatisplus.core.override.MybatisMapperProxy@2e17a321
true
public interface UserMapper extends BaseMapper<User> {}

所以又get到一个新的技能,那么可以获取得到mapper。因为在启动类上加了注解@MapperScan,所以会生成代理类到容器中去;那么代表了我们可以从service层中直接获取得到对应的mapper代理类,那么也就说明了随时使用随时获取得到即可。那么在service层中也不需要再次进行@AutoWired进来对应的mapper了,也是可行的。

没有针对于insert的lambda对应的操作,所以如果想要使用insert操作的时候,可以直接调用service中的手动批量操作即可。总之来说吹捧一波lambda来操作数据库。

查询所有的:

@Test
public void testLambdaQueryList(){
    List<User> userList = userService.lambdaQuery().list();
    userList.forEach(System.out::println);
}

根据条件来进行查询:

    @Test
    public void testLambdaQueryListByCondition(){
        List<User> userList = userService.lambdaQuery().eq(User::getName,"Cat").eq(User::getAge,22).list();
        userList.forEach(System.out::println);
    }

这里如果有多条数据,那么会在这里显示多条数据。

查询出来一条数据,和上面的来进行对比:

    @Test
    public void testLambdaQueryOneByCondition(){
        User cat = userService.lambdaQuery().eq(User::getName, "Cat").eq(User::getAge, 29).one();
        System.out.println(cat);
    }

如果这里因为条件不够,可能查询出来的数据有多条或者是一条数据。如果是多条数据,那么会爆出TooManyResultsException错误。

所以在使用的时候应该确保这里的记录是多条还是一条数据。

那么看一下这里的重载使用方式:

    @Test
    public void testLambdaQueryOneByCondition(){
        User cat = userService.lambdaQuery().eq(User::getName, "guang").eq(User::getAge, 24).oneOpt().orElseGet(User::new);
        System.out.println(cat);
    }

如果数据库中只有一条记录,那么使用这种方式或者是one()方法都可以。

但是如果数据库中一条记录都没有,那么查询出来的结果中肯定是没有对应的值的。所以在Optional中没有值,就代表了容器中没有对应的对象。

    @Test
    public void testLambdaQueryOneByCondition(){
        User cat = userService.lambdaQuery().eq(User::getName, "guang").eq(User::getAge, 26).oneOpt().orElseGet(()->{
            User user = new User();
            user.setAge(-1);
            user.setName(null);
            user.setEmail("default@qq.com");
            return user;
        });
        System.out.println(cat);
    }

如果数据库中没有,那么使用默认提供的值来使用。但是需要注意的是这里是将数据查询出来之后,而不会将数据插入到数据库中去;

那么测试使用完了lambda的query中提供的方法之后,那么开始测试使用lambda中的update方法。

更新操作

@Test
public void testLambdaUpdate(){
    User user = new User();
    user.setName("today");
    user.setEmail("today@qq.com");
    user.setAge(18);
    // 一种使用方式
    boolean update = userService.lambdaUpdate().setEntity(user).update();
}

但是这种使用方式对于我们来说,可能我们并不想写对应的实体类。但是有时候如果上下文中存在着对应的实体类,那么我们直接放入到entity中来进行使用即可。看指定接口中方法的定义:

还有一种方式可以来进行使用:

    @Test
    public void testLambdaUpdate(){
        userService.lambdaUpdate().eq(User::getAge,29).set(User::getName,"today").update();
    }

这种方式的使用适用于个别字段需要来进行修改;如果是大量字段需要来进行修改,那么推荐使用上面的setEntity来进行使用。

查询到了那么就进行update,对应的SQL语句如下所示:

UPDATE user SET name=? WHERE (age = ?)

那么测试一下其中的删除方法:

    @Test
    public void testLambdaRemove(){
        userService.lambdaUpdate().eq(User::getAge,29).remove();
    }

对应的SQL语句显示:

DELETE FROM user WHERE (age = ?)
    @Test
    public void testLambdaRemove1(){
        AbstractWrapper<User, SFunction<User, ?>, LambdaQueryWrapper<User>> wrapper = userService.lambdaQuery().getWrapper();
        System.out.println(wrapper.toString());
        List<User> list1 = userService.lambdaQuery().list();
        System.out.println("没有设置条件之后查询出来的数量是---------->>>>"+list1.size());
        // 事实证明,这里如果已经设置了条件,那么依然不会对原有的结构中的生效。这样子来说是不会生效的
        wrapper.eq(User::getAge,21).eq(User::getName,"testSave");
        // 这里点击进来看一下这里的构造,重新创建了新的wrapper来进行使用了
        List<User> list2 = userService.lambdaQuery().list();
        List<User> list3 = userService.list(wrapper);
        System.out.println(list3);
        System.out.println("设置条件之后查询出来的数量是---------->>>>"+list3.size());
        wrapper.clear();
        wrapper.eq(User::getName,"testSave");
        List<User> list4 = userService.list(wrapper);
        System.out.println(list4);
    }

上面这种方式不推荐来进行使用!可能会出现问题

从这里可以总结出来两点:

1、wrapper可以被重复使用,如果下面还需要使用到,但是不需要使用到上面的条件表达式,那么可以进行清除掉之后,继续来进行使用。

2、对于 userService.lambdaQuery()来说,每次都会创建新的wrapper来进行使用。

4.4、分页查询

    /**
     * 根据分页来进行查询得到对应的list
     * 需要注意的是要实现分页插件,不然里面的total或者是records可能没有
     * @return
     */
    @Override
    public List<ReviewContent> findPageList() {
        Page<ReviewContent> page1 = new Page<>(1,10);
        LambdaQueryWrapper<ReviewContent> lambdaQueryWrapper = Wrappers.<ReviewContent>lambdaQuery().eq(ReviewContent::getEleId, 11682);
        ReviewContentServiceImpl reviewContentService = CastUtils.cast(this);
        Page<ReviewContent> page = reviewContentService.page(page1, lambdaQueryWrapper);
        // 这两个page是同一个page而已
        logger.info("两个page之间的关系是:{}",page==page1);
        // 每页显示条目数
        long currentSize = page.getSize();
        logger.info("当前页有{}条数据",currentSize);
        // 总共有多少条数据
        long total = page.getTotal();
        logger.info("当前一共有{}条数据",total);
        // 获取得到当前页
        long currentPage = page.getCurrent();
        logger.info("当前页是第{}页",currentPage);
        // 获取得到所有的页数
        long totalPage = page.getPages();
        logger.info("总共有{}页",totalPage);
        // 得到所有的数据即可
        List<ReviewContent> records = page.getRecords();
        logger.info("当前的记录是:{}", JSON.toJSONString(records));
        return null;
    }

5、AR模式

ActiveRecord模式,通过操作实体对象,直接操作数据库表。类似Hibernate来进行操作了。这个就比较简单了,可以直接查看一下里面的对应的方法参考即可。我这里列举出来几个值来进行参考。

如果想要使用这种方式,那么只需要让实体类来Model。那么分别来举例子来进行说明一下:

    @Test
    public void testInsert(){
        User user  = new User();
        user.setEmail("AR");
        user.setAge(22);
        user.setEmail("AR@qq.com");
        user.insert();
        System.out.println(user); // User(id=1472113452501647362, name=null, age=22, email=AR@qq.com)
    }
    /**
     * 没有主键会报错
     */
    @Test
    public void testUpdate(){
        User user  = new User();
        user.setEmail("ARUpdate");
        user.setId(1472113452501647362L);
        user.setEmail("ARUpdate@qq.com");
        user.updateById();
        System.out.println(user);
    }
    @Test
    public void testQuery(){
        User user  = new User();
        user.setEmail("ARUpdate");
        user.setId(1472113452501647362L);
        user.setEmail("ARUpdate@qq.com");
        user.updateById();
        List<User> userList = user.selectAll(); // SELECT id,name,age,email FROM user
        userList.forEach(System.out::println);
    }
    @Test
    public void testDelete(){
        User user  = new User();
        user.setId(1472113452501647362L); // DELETE FROM user WHERE id=?
        user.deleteById();
    }

批量插入

那么紧接着创建出来对应的实现类:

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {

}

那么查看这个实现类,实现接口。然后让当前类来集成一个父类,这个父类是mp提供的。

posted @ 2021-12-18 16:05  写的代码很烂  阅读(2405)  评论(0编辑  收藏  举报