Mybatis Plus 深入使用及代码生成器(新)
之前那篇关于代码生成器的博客的只适用于 mybatis-plus-generator 3.5.1 以下版本,本文将讲解新的代码生成器的使用及 MP(Mybatis Plus , 下文通称 MP) 的深入使用 。
关于引入依赖,本文不再赘述,有需要请翻阅以往博客,或前往官网:MP安装
表结构
User 表及 User_id_card 表:
代码生成器
##代码生成器启动前:
代码生成器实现:
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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| package com.youfeng.MybatisPlus.utils;
import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.generator.FastAutoGenerator; import com.baomidou.mybatisplus.generator.config.*; import com.baomidou.mybatisplus.generator.config.rules.DbColumnType; import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine; import com.baomidou.mybatisplus.generator.fill.Column;
import java.sql.Types; import java.util.Collections;
public class CodeGenerator { public static void main(String[] args) { FastAutoGenerator.create("jdbc:mysql://47.120.5.13:3306/blog" + "?useUnicode=true&characterEncoding=utf-8" + "&useSSL=true&serverTimezone=Asia/Shanghai", "<your database username>", "<your database password>") .globalConfig(builder -> { builder.author("侑枫") .disableOpenDir() .commentDate("yyyy-MM-dd HH:mm:ss") .outputDir(System.getProperty("user.dir") + "\\src\\main\\java"); }) .dataSourceConfig(builder -> builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> { int typeCode = metaInfo.getJdbcType().TYPE_CODE; if (typeCode == Types.SMALLINT) { return DbColumnType.INTEGER; } return typeRegistry.getColumnType(metaInfo); })) .packageConfig(builder -> { builder.parent("com.youfeng.MybatisPlus") .entity("pojo") .pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + "\\src\\main\\resources\\com\\youfeng\\MybatisPlus")); }) .strategyConfig(builder -> { builder.addInclude("user", "user_id_card")
.entityBuilder() .enableLombok() .enableTableFieldAnnotation() .idType(IdType.ASSIGN_ID) .addTableFills(new Column("created_time", FieldFill.INSERT)) .addTableFills(new Column("modified_time", FieldFill.INSERT_UPDATE)) .build() .controllerBuilder() .enableRestStyle() .mapperBuilder()
.build();
}) .templateEngine(new VelocityTemplateEngine()) .execute(); } }
|
该代码为可执行示例代码,具体生成器可依照需求参考官网:代码生成器配置新
执行结果部分示例
目录:
Controller:
基于RESTful API
风格思想,我们将资源改为复数形式(即将user
改为users
)
Mapper:
记得在 Mapper
层加上 @Mapper
或在 Application.java
加上 @MapperScan("这里填入mapper接口的路径")
Pojo:
Service:
ServiceImpl:
完善 application.yaml
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://47.120.5.13:3306/blog?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true username: "<your database username>" password: "<your database password>" main: banner-mode: off mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true global-config: banner: false type-aliases-package: com.youfeng.MybatisPlus.pojo logging: level: root: error server: port: 221
|
在 config
包下创建 MyMetaObjectHandler
完善 insertFill
以及 updateFill
:
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
| package com.youfeng.MybatisPlus.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.setFieldValByName("createdTime", LocalDateTime.now(), metaObject); this.setFieldValByName("modifiedTime", LocalDateTime.now(), metaObject); }
@Override public void updateFill(MetaObject metaObject) { this.setFieldValByName("modifiedTime", LocalDateTime.now(), metaObject); } }
|
深入使用
IService
在使用之前,我们先来看一下 MP 的通用接口
通用接口为IService
,默认实现为ServiceImpl
,其中封装的方法明细如下:
save
:新增save
是新增单个元素saveBatch
是批量新增saveOrUpdate
是根据id判断,如果数据存在就更新,不存在则新增saveOrUpdateBatch
是批量的新增或修改
remove
:删除removeById
:根据id删除removeByIds
:根据id批量删除removeByMap
:根据Map中的键值对为条件删除remove(Wrapper<T>)
:根据Wrapper条件删除removeBatchByIds
:根据ids批量删除
update
:更新updateById
:根据id修改update(Wrapper<T>)
:根据UpdateWrapper
修改,Wrapper
中包含set
和where
部分update(T,Wrapper<T>)
:按照T
内的数据修改与Wrapper
匹配到的数据updateBatchById
:根据id批量修改
get
:查询单个结果getById
:根据id查询1条数据getOne(Wrapper<T>)
:根据Wrapper
查询1条数据getBaseMapper
:获取Service
内的BaseMapper
实现,某些时候需要直接调用Mapper
内的自定义SQL
时可以用这个方法获取到Mapper
list
:查询集合结果listByIds
:根据id批量查询list(Wrapper<T>)
:根据Wrapper条件查询多条数据list()
:查询所有
count
:计数count()
:统计所有数量count(Wrapper<T>)
:统计符合Wrapper
条件的数据数量
page
:分页查询getBaseMapper
: 获取service对应的Mapper
LambdaQueryWrapper 及 LambdaUpdateWrapper
QueryWrapper
和 UpdateWrapper
大家用的比较多,故不再赘述。
LambdaQueryWrapper / LambdaUpdateWrapper
相比 QueryWrapper / UpdateWrapper
的改进主要在于使用 Lambda
表达式来构建查询条件, 这样做的好处有几点:
- Lambda 表达式可以在编译时进行类型检查,相比字符串传入字段名,Lambda 表达式可以更好地避免在运行时出现拼写错误或者字段名错误的情况。
- 使用 Lambda 表达式可以使代码更加简洁清晰,不需要像传入字符串那样手动拼接 SQL 条件,提高了代码的可读性和可维护性
LambdaQueryWrapper / LambdaUpdateWrapper
是对 QueryWrapper / UpdateWrapper
的改进版本,它们在向下兼容的同时保留了原有的特性,包括对条件的灵活构建和链式编程的支持。
接下来我们用下列案例来快速学习一下如何使用 LambdaQueryWrapper / LambdaUpdateWrapper
例如我们现在有这么两个需求:
需求1(查找):如果用户登录名(字段:username
)不为空,则对用户登录名进行模糊匹配;如果状态(字段:status
)不为空,则对状态进行精准匹配,且注册(创建)时间在 2023-09-03 16:22:50
到两天后之间
需求2(修改): 将状态(字段:status
)为冻结(值:1
),且注册(创建)时间为2023-05-20
或注册时间为2024-05-20
的用户的密码(字段:password
)修改为123456
,状态修改为正常(值:1
)
(此处代码仅展示 serviceImpl 层)
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
|
@Override public List<User> selectVague(User user) { QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.like(ObjectUtils.isNotEmpty(user.getUsername()), "user_name", user.getUsername()) .eq(ObjectUtils.isNotEmpty(user.getStatus()), "status", user.getStatus()) .between("create_time", LocalDateTime.of(2023, 9, 3, 16, 22, 50), LocalDateTime.of(2023, 9, 5, 16, 22, 50)); return baseMapper.selectList(queryWrapper); }
@Override public void updateUser(User user) { UpdateWrapper<User> updateWrapper = new UpdateWrapper<>(); updateWrapper.eq("status", 1) .and(i -> i.eq("create_time", LocalDate.of(2023,5,20)) .or().eq("create_time", LocalDate.of(2024,5,20))) .set("password", 123456) .set("status", 0); update(updateWrapper); }
|
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
|
@Override public List<User> selectVague(User user) { LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>(); lambdaUpdateWrapper.like(ObjectUtils.isNotEmpty(user.getUsername()), User::getUsername, user.getUsername()) .eq(ObjectUtils.isNotEmpty(user.getStatus()), User::getStatus, user.getStatus()) .between(User::getCreatedTime, LocalDateTime.of(2023, 9, 3, 16, 22, 50), LocalDateTime.of(2023, 9, 5, 16, 22, 50)); return baseMapper.selectList(queryWrapper); }
@Override public void updateUser(User user) { LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>(); lambdaUpdateWrapper.eq(User::getStatus, 1) .and(i -> i.eq(User::getCreatedTime, LocalDate.of(2023,5,20)) .or().eq(User::getCreatedTime, LocalDate.of(2024,5,20))) .set(User::getPassword, 123456) .set(User::getStatus, 0); update(lambdaUpdateWrapper); }
|
通过对比,我们可以发现,使用 Lambda 表达式构建查询条件,使得代码更为简洁,减少了代码字面量,降低了出错的可能性,同时提高了可读性和可维护性。
需要注意的是,在使用 LambdaWrapper
时,需指定类型参数,例如代码中指定该 LambdaWrapper
为 User
类型;以及 User::getUsername
中的 ::
运算符用于引用方法或构造函数。这种方法引用的方式可以在Lambda表达式中传递方法,而不必显式地编写方法的参数列表和实现体。
LambdaQuery() 及 LambdaUpdate() 及静态工具 Db
Service
中对LambdaQueryWrapper
和LambdaUpdateWrapper
的用法进一步做了简化。我们无需自己通过new
的方式来创建Wrapper
,而是直接调用lambdaQuery
和lambdaUpdate
方法。
有的时候Service
之间也会相互调用,为了避免出现循环依赖问题,Mybatis Plus
提供一个静态工具类:Db
,其中的一些静态方法与IService中方法签名基本一致,也可以帮助我们实现CRUD
功能。
简单使用
我们还是用上述相同案例,通过对比LambdaWrapper
来快速学习一下如何简单使用 LambdaQuery() / LambdaUpdate()
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
|
@Override public List<User> selectVague(User user) { LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>(); lambdaQueryWrapper.like(ObjectUtils.isNotEmpty(user.getUsername()), User::getUsername, user.getUsername()) .eq(ObjectUtils.isNotEmpty(user.getStatus()), User::getStatus, user.getStatus()) .between(User::getCreatedTime, LocalDateTime.of(2023, 9, 3, 16, 22, 50), LocalDateTime.of(2023, 9, 5, 16, 22, 50)); return baseMapper.selectList(lambdaQueryWrapper); }
@Override public void updateUser(User user) { LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>(); lambdaUpdateWrapper.eq(User::getStatus, 1) .and(i -> i.eq(User::getCreatedTime, LocalDate.of(2023,5,20)) .or().eq(User::getCreatedTime, LocalDate.of(2024,5,20))) .set(User::getPassword, 123456) .set(User::getStatus, 0); update(lambdaUpdateWrapper); }
|
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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
|
@Override public List<User> selectVague(User user) { return lambdaQuery() .like(ObjectUtils.isNotEmpty(user.getUsername()), User::getUsername, user.getUsername()) .eq(ObjectUtils.isNotEmpty(user.getStatus()), User::getStatus, user.getStatus()) .between(User::getCreatedTime, LocalDateTime.of(2023, 9, 3, 16, 22, 50), LocalDateTime.of(2023, 9, 5, 16, 22, 50)) .list(); return Db.lambdaQuery(User.class). like(ObjectUtils.isNotEmpty(user.getUsername()), User::getUsername, user.getUsername()) .eq(ObjectUtils.isNotEmpty(user.getStatus()), User::getStatus, user.getStatus()) .between(User::getCreatedTime, LocalDateTime.of(2023, 9, 3, 16, 22, 50), LocalDateTime.of(2023, 9, 5, 16, 22, 50)) .list(); }
@Override public void updateUser(User user) { lambdaUpdate().eq(User::getStatus, 1) .and(i -> i.eq(User::getCreatedTime, LocalDate.of(2023,5,20)) .or().eq(User::getCreatedTime, LocalDate.of(2024,5,20))) .set(User::getPassword, 123456) .set(User::getStatus, 0) .update(); Db.lambdaUpdate(User.class) .eq(User::getStatus, 1) .and(i -> i.eq(User::getCreatedTime, LocalDate.of(2023,5,20)) .or().eq(User::getCreatedTime, LocalDate.of(2024,5,20))) .set(User::getPassword, 123456) .set(User::getStatus, 0) .update(); }
@Override public boolean insertAny() { User user = new User(); user.setUsername("张三"); user.setPassword("123"); user.setId("1000000000000000001"); boolean saveUser = save(user); UserIdCard userIdCard = new UserIdCard(); userIdCard.setUserId(user.getId()); userIdCard.setNumber("99887766"); return saveUser && Db.save(userIdCard); }
@Override public boolean deleteById() { boolean removeUser = lambdaUpdate() .eq(User::getId, 1000000000000000001L) .remove(); boolean removeUserIdCard = Db.lambdaUpdate(UserIdCard.class) .eq(UserIdCard::getUserId, 1000000000000000001L) .remove(); return removeUser && removeUserIdCard; }
@Override public boolean updatePassword() { boolean updateUser = lambdaUpdate() .set(User::getPassword, "321") .eq(User::getId, 1000000000000000001L) .update(); boolean updateUserIdCard = Db.lambdaUpdate(UserIdCard.class) .set(UserIdCard::getNumber, "66778899") .eq(UserIdCard::getUserId, 1000000000000000001L) .update(); return updateUser && updateUserIdCard; }
|
Tips
- 在
lambdaQuery()
中的链式编程的最后一个方法决定最终的返回结果,可选的方法有:.one()
:最多1个结果.list()
:返回集合结果.count()
:返回计数结果
- 在
lambdaUpdate
中的链式编程的最后一个方法应为update()
LambdaQuery
和 LambdaUpdate
部分常用条件构造方法:eq
:等于ne
:不等于gt
:大于ge
:大于等于lt
:小于le
:小于等于or
: 或逻辑apply
: 直接拼接 SQL 语句and
: 与逻辑like
:模糊匹配likeLeft
:左模糊匹配likeRight
:右模糊匹配in
:包含于(in适合于外表大而内表子查询小的情况,exists适合于外表小而内表子查询大的情况)exists
: 存在(当in
的参数不为已知数值类别时,建议使用exist
效率更高)notIn
:不包含于isNull
:字段为 nullisNotNull
:字段不为 nulllast
:拼接在 SQL 的最后,用于自定义 SQL 片段- ……(还有很多,更多方法请查找官方文档)
LambdaQuery
专用方法:groupBy
: 分组having
:过滤分组的结果order
: 排序orderByAsc
:升序排序orderByDesc
:降序排序distinct
:去重- ……
LambdaUpdate
专用方法:set
赋值setIncrBy
(3.5.6版本新特性): 指定字段新增指定数值setDecrBy
(3.5.6版本新特性): 指定字段减少指定数值- ……
selectColumn 进阶使用(只查询部分字段)
众所周知,MySQL
中进行全表查询和仅查询部分字段的效率存在差异。全表查询(SELECT * FROM table_name)
意味着MySQL
需要检索并返回表中的所有列,这可能导致数据传输量大和查询时间长,特别是在大型表中。相比之下,仅查询部分字段(例如SELECT column1, column2 FROM table_name
)时,MySQL
只需检索和返回指定的列,从而减少了数据传输量和查询时间,因为MySQL
无需检索不需要的列数据。因此,仅查询部分字段通常比全表查询更有效率,特别是当表中包含大量列或某些列的数据量较大时。
另外,许多业务实际上并不需要查询所有字段,有时只需要特定的字段,但却检索出一大堆数据,这不仅影响效率,还显得不够优雅。而在 MP
中,想要查找部分字段,仅需简单的一个select
方法就可以解决。例如下列代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
@Override public User queryAnyColumnById(String id) { return lambdaQuery() .eq(User::getId, id) .select(User::getUsername) .one(); return Db.lambdaQuery(User.class) .eq(User::getId, id) .select(User::getUsername, User::getStatus, User::getInfo) .one(); }
|
selectFunc 进阶使用(SQL函数)
在实际业务中,我们经常需要使用 GROUP BY
和 SUM
函数来进行统计和汇总计算。然而,在目前的 MP(SINCE 3.5.6)
中,除了.count()
构造方法外,它暂时没有提供直接支持 SQL
函数的构造方法。那么,我们应该如何在 MP
中实现对 SQL
函数(selectFunc
)的使用呢?
接下来我们通过几个案例来学习一下如何使用 selectFunc
需求1:统计注册(创建)时间在2023-5-20
至2024-5-20
之间状态(字段:status
)为正常(值:1
)的用户的考核分(字段:score
)的总合。
需求2:统计注册(创建)时间在2023-5-20
至2024-5-20
之间状态(字段:status
)为正常(值:1
)的用户的考核分(字段:score
)的平均值。
需求3: 查找注册(创建)时间在2023-5-20
至2024-5-20
之间状态(字段:status
)为正常(值:1
)的用户的考核分(字段:score
)的最大值和最小值。
首先在 user
实体类中加以下字段 sumScore
,aveScore
,maxScore
,minScore
,并使用 @TableField 注解来指定相应的聚合函数表达式,并将 select
属性设置为 false
,告诉 MP
在默认查询时忽略该字段,这是我们自定义的聚合运算,目的是作为辅助属性来使用。
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
| ......
@TableField("score") private Integer score;
@TableField(value = "SUM(score)", select = false) private Integer sumScore;
@TableField(value = "AVG(score)", select = false) private Integer aveScore;
@TableField(value = "MAX(score)", select = false) private Integer maxScore;
@TableField(value = "MIN(score)", select = false) private Integer minScore;
@TableField("info") private String info;
......
|
接下来,我们要通过 lambdaQuery() 依次调用 between
、eq
、groupBy
方法来构建查询条件,使用 select
方法来使用我们的聚合函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public User queryFuncScore () { return lambdaQuery() .between(User::getCreatedTime, LocalDate.of(2023, 5, 20), LocalDate.of(2024, 5, 20)) .eq(User::getStatus, "1") .select(User::getSumScore, User::getAveScore, User::getMaxScore, User::getMinScore) .groupBy(User::getStatus) .having("status = 1") .one(); }
|
通过这种方式,我们可以完成我们所需要的selectFunc查询操作。
setIncrBy/setDecrBy 进阶使用(自增自减)
在我们的一些业务中,经常需要对某些字段进行复杂的自增或自减操作。在过去的 MP
版本中,要实现这样的操作,通常需要使用 setSql
方法来指定更新的字段拼接相应的 SQL
表达式来实现自增或自减操作。或者,我们可能需要先查询字段的当前值,然后根据需要增加或减少特定数量。然而,在 MP 3.5.6
版本中,新增了两个非常方便的特性:setIncrBy
(自增)和 setDecrBy
(自减)。接下来,让我们通过对比来学习如何使用这两个新特性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
@Override public void increaseScore(String userId, int increment) { lambdaUpdate().eq(User::getId, userId) .setSql("`score` = `score` + " + increment) .update(); }
@Override public void decreaseScore(Long userId, int decrement) { lambdaUpdate().eq(User::getId, userId) .setSql("`score` = `score` - " + decrement) .update(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
@Override public void increaseScore(String userId, int increment) { lambdaUpdate().eq(User::getId, userId) .setIncrBy(User::getScore, increment) .update(); }
@Override public void decreaseScore(Long userId, int decrement) { lambdaUpdate().eq(User::getId, userId) .setDecrBy(User::getScore, decrement) .update(); }
|
枚举类字段处理器
在我们的User
表中,有一个表示状态的字段 status
,其取值仅限于0(表示正常)和1(表示冻结)。通常情况下,针对这种具有多个固定取值的字段,我们会定义一个枚举类型,这样在进行业务判断时可以直接基于枚举进行比较。然而,我们的数据库采用的是int
类型来存储这个字段,对应的持久化对象也是Integer
类型。因此,在进行业务操作时,我们必须手动进行枚举与整数之间的转换,这一过程相当繁琐。
而 MP
提供了一个处理枚举的类型转换器,可以帮我们把枚举类型与数据库类型自动转换。
首先我们需要定义枚举,在 enums
包下创建一个UserStatus
枚举类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
@Getter public enum UserStatus {
NORMAL((byte)0,"正常"),
FREEZE((byte)1,"冻结");
private final byte status; private final String desc;
UserStatus(byte status, String desc) { this.status = status; this.desc = desc; } }
|
接下来将修改 User
实体类中 status
的类型为 UserStatus
类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| ......
@TableField("status") private String status;
@TableField("info") private String info; ......
|
要让MP
处理枚举与数据库类型自动转换,我们必须告诉MP
,枚举中的哪个字段的值作为数据库值。使用MP
提供了@EnumValue
注解来标记枚举属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
@Getter public enum UserStatus {
NORMAL((byte)0,"正常"),
FREEZE((byte)1,"冻结"); @EnumValue private final byte status; @JsonValue private final String desc;
UserStatus(byte status, String desc) { this.status = status; this.desc = desc; } }
|
最后,我们需要在 application.yaml
中配置一下枚举处理器
1 2 3 4 5 6 7 8 9 10 11
| ...... mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl map-underscore-to-camel-case: true default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler global-config: banner: false type-aliases-package: com.youfeng.MybatisPlus.pojo ......
|
至此,我们的枚举类字段处理器配置完成了,接下来我们来测试一下是否成功。
首先看一下我们的数据库当前的数据:
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
|
@RestController @RequestMapping("/users") @RequiredArgsConstructor public class UserController { private final IUserService userService;
@GetMapping public List<User> getList() { List<User> list = userService.lambdaQuery().list(); list.forEach(System.out::println); return list; } @PostMapping public void saveUser() { User user = new User(); user.setUsername("wangwu"); user.setPassword("123"); user.setStatus(UserStatus.NORMAL); userService.save(user); } }
|
使用 PostMan
调用查询接口,并在返回结果前打断点得到数据:
可以发现,我们查询出的User
类的status
字段会是枚举类型。
接下来继续使用 PostMan
测试一下保存接口:
查询数据库:
可以看到枚举类处理器成功实现了我们的查询和插入。
JSON 字段类型处理器
数据库的user表中的info
字段,是JSON
类型(MariaDB
数据库无 JSON
类型字段,为longtext
)。一般 JSON
格式类似这样:
1
| {"age": 24, "sex": "male", "address": "xiamen"}
|
但实体类中的 info
为 String
类型。这样一来,我们要读取info中的属性时就非常不方便。而MP
提供了很多特殊类型字段的类型处理器,解决特殊字段类型与数据库类型转换的问题。例如处理JSON
就可以使用JacksonTypeHandler
处理器。下面我们来实现一下这个 JSON
字段类型处理器。
首先定义一个与 info
字段与属性匹配的实体类:
1 2 3 4 5 6 7 8 9 10 11 12
|
@Data @NoArgsConstructor @AllArgsConstructor(staticName = "of") public class UserInfo { private Integer age; private String sex; private String address; }
|
接下来,将 User
类的 info
字段修改为 UserInfo
类型,并声明为 JSON
类型处理器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Data @TableName(value = "user", autoResultMap = true) public class User implements Serializable {
private static final long serialVersionUID = 1L;
......
@TableField("status") private UserStatus status;
@TableField(value = "info",typeHandler = JacksonTypeHandler.class) private UserInfo info; }
|
请注意,为了实现对象查询的嵌套,我们必须采用 ResultMap
。因此,在User
实体类中,需要在@TableName
注解中添加 autoResultMap = true
参数。
到这里,JSON
字段类型处理器已经配置完成了,我们来测试一下是否配置成功。
我们在 Controller
层写一个保存和查询方法:
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
|
@RestController @RequestMapping("/users") @RequiredArgsConstructor public class UserController { private final IUserService userService;
@PostMapping public void saveUser() { User user = new User(); user.setUsername("zhaoliu"); user.setPassword("123"); user.setStatus(UserStatus.NORMAL); UserInfo userInfo = new UserInfo(); userInfo.setAge(24); userInfo.setSex("male"); userInfo.setAddress("xiamen"); userService.save(user); }
@GetMapping("/{id}") public User getUserById(@PathVariable String id) { User user = userService.lambdaQuery() .eq(User::getId, id) .one(); System.out.println(user); return user; } }
|
当前数据库表:
我们依旧使用 PostMan
,先来请求一下保存接口:
数据库查询结果:
接下来继续使用 PostMan 测试一下查询接口:
可以看到 JSON
处理器成功实现了我们的查询和插入。
配置加密
在我们的配置文件中,很多参数都是以明文形式存储的,这可能会导致敏感信息的泄露问题,MP
中提供了配置文件加密和解密的功能。
我们以数据库的参数为例来演示如何使用:
首先使用 AES
的generateRandomKey()
和 encrypt()
方法分别获取密钥和参数对应的密文
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class ApplicationTests { @Test void encrypt() { String randomKey = AES.generateRandomKey(); System.out.println("randomKey = " + randomKey);
String url = AES.encrypt("<your url>", randomKey); System.out.println("url = " + username);
String username = AES.encrypt("<your username>", randomKey); System.out.println("username = " + username);
String password = AES.encrypt("<your password>", randomKey); System.out.println("password = " + password); } }
|
将用户和密码输入后执行,可以得到密钥和加密过后的用户名密码,例如:
1 2 3 4
| randomKey = 4576******dfabca url = j2H2dMKcpKzGWyaCNDCzGWSskCFAcLhasUBgjOGxKTc1/Pvr+HPCk3uN+7Gz2aqMDXX4uKlqdcsy6Xi2vwwAuP6SZb8WiSf4mXAv3bAiqWKo1AebEaUlFYPwPobM3Mc7ZEcIk2Z32Vx12cuJx+ZrJNMvKF2HI+9/3rXZlATLWH8= username = GmfwxaA8LojLrjsvC7g+qw== password = qa2K2V/toVHF4A41N+2chg==
|
接下来修改 application.yaml
文件,把对应的属性修改为刚刚加密生成的密文:
1 2 3 4 5 6
| spring: datasource: url: mpw:j2H2dMKcpKzGWyaCNDCzGWSskCFAcLhasUBgjOGxKTc1/Pvr+HPCk3uN+7Gz2aqMDXX4uKlqdcsy6Xi2vwwAuP6SZb8WiSf4mXAv3bAiqWKo1AebEaUlFYPwPobM3Mc7ZEcIk2Z32Vx12cuJx+ZrJNMvKF2HI+9/3rXZlATLWH8= driver-class-name: com.mysql.cj.jdbc.Driver username: mpw:GmfwxaA8LojLrjsvC7g+qw== password: mpw:qa2K2V/toVHF4A41N+2chg==
|
请注意:密文需要以 mpw:开头
最后,在项目启动时,把刚才生成的秘钥添加到启动参数中,例如:
1
| --mpw.key=4576******dfabca
|
END