微服务开发系列:开篇
微服务开发系列:为什么选择 kotlin
微服务开发系列:为什么用 gradle 构建
微服务开发系列:目录结构,保持整洁的文件环境
微服务开发系列:服务发现,nacos 的小补充
微服务开发系列:怎样在框架中选择开源工具
微服务开发系列:数据库 orm 使用
微服务开发系列:如何打印好日志
微服务开发系列:鉴权
微服务开发系列:认识到序列化的重要性
微服务开发系列:设计一个统一的 http 接口内容形式
微服务开发系列:利用异常特性,把异常纳入框架管理之中
微服务开发系列:利用 knife4j,生成最适合微服务的文档
系统中使用 mybatis plus 框架作为关系型数据库的 orm 框架。
1 mybatis plus 使用
1.1 不使用 IService
mp 使用提供了一个 IService
类,作为业务层的扩展。
但是框架中不建议这种将业务代码和基础功能混合的做法,这会导致业务的灵活性下降,耦合度过高。
在我理解的 spring boot
设计中,倾向于让开发人员的业务代码尽量扁平化,不同类型的功能,更加建议你使用多个类的方式,然后通过注入这个类获取不同类型的功能。
因此同样的 controller 层也不建议使用继承来增加额外的功能。
为了补充一些 IService
的功能,框架中提供了 PBaseMapper
来增强 BaseMapper。
自己做增强类的好处之一就是灵活。
PBaseMapper
还提供了一个 ktUpdate
的方法,返回了经过 Enhancer
代理的 KtUpdateChainWrapper
变量。
它的作用是当使用 set
方法时,判断一个字段是否有 TableField
注解,如果有则自动修改 mapper
参数,增加 typeHandler
配置,来防止 mybatis plus 在更新时传递给 ibatis 的复杂对象序列化失败的问题(详情见 issue)。
1.2 配置增强
关于 mybatis plus,框架中做了统一的配置增强,路径在 business-foundation:cn.business.foundation.config.mybatis
中。
下面对这几个类进行说明。
1.2.1 BaseEntity
基础实体类,包含如下的字段。
字段 | 含义 |
---|---|
createUserId | 创建人 ID |
createTime | 创建时间 |
modifyUserId | 修改人 ID |
modifyTime | 修改时间 |
基础实体类不要求强制继承,可以自己选择。
1.2.2 MetaObjectHandlerConfig
自动装载类,配置审计功能,当实体类包含上述四个字段时,会做相应的字段填充。
1.2.3 MybatisPlusConfig
自动状态类,配置与 mybatis plus 相关的拦截器或者其它配置。
1.2.4 PBaseMapper
BaseMapper
的增强实现,代替 mybatis plus 提供的 com.baomidou.mybatisplus.extension.service.IService
, IService
原本是用于继承给业务中的 Service 层使用,增强数据库的能力,但是在这里认为,Service 层不适合继承这种类型的类。
1.3 jpa like 能力
mybatis plus 基于 mybatis 做的功能强化,能够在单表简单操作上能够达到与 jpa 非常接近的水平。
也就是说,如果合理的运用 mybatis plus 提供的扩展能力,在切换数据库时,能够有效的降低迁移成本,比如下面这段代码。
User user = userMapper.lambdaQuery()
.select(User::getPassword)
.eq(User::getAccount, account)
.oneOpt()
.orElseThrow(() -> {
log.warn("account not found {}", account);
return new InnerExp("用户名或密码错误");
});
在代码中没有使用任何的 sql 即可完成查询,但是这种操作仅限于单表操作,复杂的多表查询必须用到 xml 写 sql。
在上面查询中,请注意到
User::getAccount
这样的用法,在代码中尽量避免直接使用常量字符串,这样你在修改某个字段的名称时,就能够利用现代 IDE 提供的修改能力,将所用引用统一修改。例如当你希望获取到
account
时,可以使用User::getAccount.name
。
2 分页
2.1 PageParam
路径 cn.business.foundation.common.page.PageParam
。
用于接收前端的分页和排序参数,在接收数据时可以使用这种方式接收,把分页参数和其它参数分成两个变量接收,这样就不用在每个你要接收的变量上再继承一个分页类,这样能够更加的灵活。
@GetMapping("/query")
public RequestResult selectUsers(PageParam pageParam, UserDTO userDTO) {
return userService.selectUsers(pageParam, userDTO);
}
PageParam
还应该提供了转化成查询所需的分页对象,比如框架中的 PageParam
就提供了 mybatis-plus 的分页对象,后续如果还有其它类型的查询,比如 elasticsearch 查询,也应该在这里提供相应的分页对象,这样就能做到分页参数的统一的。
在分页中可以看出,绝大部分类型的查询,参数上实际只有三类:
- 页数与条数
- 排序列
- 排序方向
完全可以使用一个类来代替,spring data 实际上就提供了一个非常好的范例,jpa 与 elasticsearch 等其他 data 架构下面的实现,都是依据这个范例来做到统一的,这个思想同样也能扩展到其它需要分页的查询。
还应该注意到,在包 cn.vte.business.foundation.common.page
下,提供了一个文件 MybatisPlusPage
,提供了 PageParam
到 mybatis plus page
的转换扩展函数。
如果将其写入到 PageParam
中,有些模块在没有依赖 mybatis plus 的情况下,会导致 mybatis plus page
的相关类找不到。
相同的思路同样适用于 elasticsearch。
这也是 kotlin 扩展函数带来的设计上的优点。
2.2 PageResult
路径 cn.business.foundation.common.page.PageResult
。
有分页参数就有分页结果,这个类就是用于返回分页结果的。设置为 RequestResult 的 value 即可。
与 2.1 中使用的思想一样,适用于分页的返回结果,都应该在这个类中转化一遍,方便统一输出。
如果涉及到其它数据库的类型转换,做法类似于 cn.vte.business.foundation.common.page.MybatisPlusPage
,使用扩展的方式提供新的类型转换方式,防止出现 ClassNotFoundException
异常。
3 mapper xml 文件位置
在框架中,要求 mapper xml 与实例类文件放置在一起。在一些项目中常常见到 xml 是放在 resources 下面的,这样我觉得跳转起来,会导致目录较乱,所以这里把 java 和 xml 放在一起,开发起来方便一点。
拷贝 xml 文件到编译位置的配置在 server:build.gradle.kts
中。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。