初识MyBatis-Plus
前言
在前面的Java学习笔记中,我提到过两次MyBatis-Plus,可能大家已经对这个名词有些耳熟。我对它的定义是:一个方便的操作数据库功能的工具包。
在它的官方文档上写道:MyBatis-Plus是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。MyBatis与MyBatis-Plus都是ORM框架(对象-关系映射框架),对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据。内存中的对象之间存在关联和继承关系,而在数据库中,关系数据无法直接表达多对多关联和继承关系。因此,对象-关系映射框架(ORM)一般以中间件的形式存在,主要实现对象到关系数据库数据的映射。
当我们编写一个应用程序时,我们可能会写特别多数据访问层的代码,从数据库添加、删除、读取对象信息,而这些代码都是重复的,如果使用ORM则会大大减少重复性代码。在使用MyBatis-Plus的时候,我总是为之惊叹:原来数据库操作能这么简单!于是我写下这篇文章记录它的一些使用方法。
应用
将MyBatis-Plus引入Spring项目非常容易,网上有许多实例,在此不做讲解。接下来,我主要介绍如何使用MyBatis-Plus的条件构造器编写业务逻辑。大体上,MyBatis-Plus从数据库表到前端接口的业务流程是数据库表->DTO->Mapper->Service->Controller,因此,我将顺着这条流程依次讲解每个环节要使用MyBatis-Plus编写哪些代码。
数据库表
我以一个存储测量设备的数据库表为例,编写与它相关的增删改查接口。下面是它的表结构:
MonitoringDevice
| 字段 | 数据类型 | 注释 |
|---|---|---|
| id | bigint | 设备id |
| name | varchar | 设备名 |
| model | varchar | 设备型号 |
| type | varchar | 设备类型 |
| illustrate | varchar | 说明 |
| location | varchar | 设备位置 |
| accuracy | varchar | 测量精度 |
| frequency | varchar | 校正频率 |
此外,还有一些与该表存在关联的其他数据表,主要通过外键与其关联,在此不做举例。
DTO
DTO即数据传输对象,是数据从后端传输到前端的载体。大体上,一个DTO要包含一个数据库表的部分或所有字段信息。为了减少重复代码,我将这些表的重复字段单列出一个ModelDTO,让其他的DTO继承这个ModelDTO。建议使用Lombok插件提供的@Data注解,可以为DTO的私有成员自动生成getter和setter方法。
ModelDTO.java
1 |
|
MonitoringDeviceDTO.java
1 |
|
Mapper
Mapper的编写非常简单,只需继承BaseMapper即可,以MonitoringDeviceMapper为例:
MonitoringDeviceMapper.java
1 |
|
Service
事实上,这些Mapper继承BaseMapper后已经为我们提供了默认的CRUD接口和一些默认方法。当这些接口和方法不满足我们需要的功能时,就需要自行编写Service。
CRUD是指做计算处理时的增加(Create)、读取查询(Retrieve)、更新(Update)和删除(Delete)几个单词的首字母简写。代表了数据库或持久层的基本操作功能。
定义Service类时,在私有变量Mapper前加上@Autowired注解。
MonitoringDeviceService.java
1 |
|
增加和编辑数据
首先是增加和编辑数据的逻辑。这里为了便于维护代码,我定义了RequestBody作为两个方法的入参。
在增加方法中,我添加了判断入参字段是否合法的逻辑,然后调用insert()方法插入一行数据。
由于DTO与RequestBody都是JavaBean,可以使用BeanUtils的copyProperties()方法将RequestBody的参数复制给DTO。
1 | public Boolean addDevice(AddMonitoringDeviceReq device) { |
在编辑方法中,使用链式条件构造器LambdaQueryChainWrapper根据传入的实体id查询Device表中是否有对应的数据,eq()定义了一个相等条件进行查询,再使用exists()方法返回布尔值,以判断查询到的数据是否存在,最后调用insert()方法即可。
注意:
insert()会自动根据数据是否存在,即插入的是否为重复行,若不存在则新增一条数据,若存在则编辑该条数据。
1 | public MonitoringDeviceDTO editDevice(EditMonitoringDeviceReq device) { |
查询数据
然后是查询数据的逻辑。首先是最简单的根据id获取数据,只需调用selectById()默认方法即可。
1 | public MonitoringDeviceDTO getDeviceById(Long deviceId) { |
之后是根据Determine表的字段查询数据。由于Determine表是通过外键deviceId与该表的id进行关联的,要查询Device表的数据,首先要获得Determine表的deviceId字段,按照这个逻辑编写条件构造器即可,使用one()返回一行数据,之后调用前面的getDeviceById()方法,传入获得的deviceId。
1 | public MonitoringDeviceDTO getDeviceByParamId(Long paramId) { |
然后是根据设备类型批量获取数据,同样使用eq()定义相等查询条件,使用list()返回多行数据,以list数组的形式作为返回值。
1 | public List<MonitoringDeviceDTO> getDeviceByType(String type) { |
最后是一个相对复杂的多表联合查询的方法。根据Source表的id获取与之关联的若干个CalcParam,再依次获取这些CalcParam的id,存入paramIdList,然后在Determine表中查询ObtainingMethod字段为2、且CalcParamId与刚才paramIdList匹配的数据行,最后根据这些Determine获取设备。
在上面这串逻辑中,除了前面提到的eq()相等条件外,还使用到了in()匹配字段条件,并在其中穿插使用Stream处理和转换数据,是一次MyBatis-Plus与Stream的综合运用。
1 | public List<MonitoringDeviceDTO> getDeviceBySource(Long sourceId) { |
删除数据
最后是删除数据的逻辑,删除Device表的数据同时要删除与其关联的MaintenanceRecord表数据,先根据id查询MaintenanceRecord表中拥有与之相同的deviceId外键的数据,再转换成id,使用deleteBatchIds()方法批量删除即可,最后调用deleteById()方法删除设备。
1 | public Boolean deleteDevice(Long deviceId) { |
Controller
一般使用Spring框架的RestController向前端提供接口,根据不同的接口类型添加@PostMapping、@GetMapping等注解。同时通过Swagger UI展示和调试接口。
MonitoringDeviceController.java
1 |
|
条件构造器的其他方法
| 方法名 | SQL | 实例 | SQL实例 |
|---|---|---|---|
| eq | 等于= | eq(“name”, “老王) | name = ‘老王’ |
| ne | 不等于<> | ne(“name”, “老王) | name <> ‘老王’ |
| gt | 大于> | gt(“age”, 18) | age > 18 |
| e | 大于等于>= | ge(“age”, 18) | age >= 18 |
| t | 小于< | It(“age”, 18) | age < 18 |
| e | 小于<= | le(“age”, 18) | age <= 18 |
| between | BETWEEN 值1 AND 值2 | between(“age”, 18, 30) | age between 18 and 30 |
| notBetween | NOT BETWEEN 值1 AND 值2 | notBetween(“age”, 18, 30) | age not between 18 and 30 |
| like | LIKE ‘%值%’ | like(“name”, “王”) | name like ‘%王%’ |
| notLike | NOT LIKE ‘%值%’ | notLike(“name”, “王”) | name not like ‘%王%’ |
| likeLeft | LIKE ‘%值’ | likeLeft(“name”, “王”) | name like ‘%王’ |
| likeRight | LIKE ‘值%’ | likeRight(“name”, “王”) | name like ‘王%’ |
| isNull | 字段 IS NULL | isNull(“name”) | name is null |
| isNotNull | 字段 IS NOT NULL | isNotNull(“name”) | name is not null |
| in | 字段 IN (v0, v1, …) | in(“age”, {1, 2, 3}) | age in (1, 2, 3) |
| notIn | 字段 NOT IN (v0, v1, …) | notIn(“age”, {1, 2, 3}) | age not in (1, 2, 3) |
| inSql | 字段 IN(sql语句) | inSql(“id”, “select id from table where id < 3”) | id in (select id from table where id < 3) |
| notInSql | 字段 NOT IN (sql语句) | notInSql(“id”, “select id from table where id < 3”) | age not in (select id from table where id < 3) |
| groupBy | 分组 GROUP BY 字段, … | groupBy(“id”, “name”) | group by id, name |
| orderByAsc | 排序 ORDER BY 字段, … ASC | orderByAsc(“id”, “name”) | order by id ASC, name ASC |
| orderByDesc | 排序 ORDER BY 字段, … DESC | orderByDesc(“id”, “name”) | order by id DESC, name DESC |
| orderBy | 排序 ORDER BY 字段, … | orderBy(true, true, “id”, “name”) | order by id ASC, name ASC |
| having | HAVING (sql语句) | having(“sum(age) > {0}”, 11) | having sum(age) > 11 |
| or | 拼接 OR | eq(“id”, 1).or().eq(“name”, “老王”) | id = 1 or name = ‘老王 |
| and | AND 嵌套 | and(i -> i.eq(“name”, “李白”).ne(“status”, “活着”)) | and (name = ‘李白’ and status <> ‘活着’) |
| apply | 拼接sql | apply(“date_format(dateColumn, ‘%Y-%m-%d’)={0}”, “2008-08-08”) | date_format(dateColumn,’%Y-%m-%d’) = ‘2008-08-08’) |
| last | 无视优化规则直接拼接到sql的最后 | last(“limit 1”) | |
| exists | 拼接 EXISTS (sql语句) | exists(“select id from table where age = 1”) | exists (select id from table where age = 1) |
| notExists | 拼接 NOT EXISTS (sql语句) | notExists(“select id from table where age = 1”) | not exists (select id from table where age = 1) |
| nested | 正常嵌套不带AND或者0R | nested(i -> i.eq(“name”, “李白”). ne(“status”, “活着”)) | (name = ‘李白’ and status <> ‘活着’) |
小结
MyBatis-Plus从数据库表到前端接口的业务流程是数据库表->DTO->Mapper->Service->Controller;DTO是数据传输对象,表示数据库里的关系数据;Mapper封装了基础的CRUD接口,提供了基础的数据库操作;Service在Mapper的基础上提供条件构造器,便于我们编写复杂的数据库操作;Controller将接口提供给前端调用,通常使用Spring提供的RestController。
非常感谢你的阅读,辛苦了!
参考文章: (感谢以下资料提供的帮助)





















