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提供的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?