MyBatis-Plus: 谨慎入坑

一、文档垃圾

MyBatis-Plus给我的第一观感是文档垃圾, 官方文档似乎还比较推崇不知道的就去读源码, 这实在是国内一些“源码论”人士的糟粕思想. 因为文档缺乏相关的信息, 或者更新不及时, 或者设计不合理, 以至于用户不得不去通过读源码去完成工作

以下是“条件构造器”的文档说明:

  • 以下出现的第一个入参boolean condition表示该条件是否加入最后生成的sql中
  • 以下代码块内的多个方法均为从上往下补全个别boolean类型的入参,默认为true
  • 以下出现的泛型Param均为Wrapper的子类实例(均具有AbstractWrapper的所有方法)
  • 以下方法在入参中出现的R为泛型,在普通wrapper中是String,在LambdaWrapper中是函数(例:Entity::getId,Entity为实体类,getId为字段idgetMethod)
  • 以下方法入参中的R column均表示数据库字段,当R具体类型为String时则为数据库字段名(字段名是数据库关键字的自己用转义符包裹!)!而不是实体类数据字段名!!!,另当R具体类型为SFunction时项目runtime不支持eclipse自家的编译器!!!
  • 以下举例均为使用普通wrapper,入参为MapList的均以json形式表现!
  • 使用中如果入参的Map或者List,则不会加入最后生成的sql中!!!
  • 有任何疑问就点开源码看,看不懂函数点击我学习新知识

你看这些描述, 完全是一脸懵逼. 下面就是各种函数的列表和参数说明, 就是一些自动生成的文档, 没什么用处.

我来打个比方, 比如第一个 boolean condition, 很多地方都有, 以上的说明里就提了一下.

我的第一反应是: “你说的这个谁懂啊!” 实际上它的作用是这样:

你经常会写这样的代码:

if (StringUtils.isNotBlank(name)) {
    query.like(Entity::getName, name)
}
if (age != null && age >= 0) {
    query.eq(Entity::getAge, age)
}

就是如果没有传name参数, 其实是没有必要添加这个条件的. 满足一定条件才会把查询条件加上去. 写的多了, 就很麻烦, 而用MyBatis-Plus的构造器, 你就可以这么写:

query.like(StringUtils.isNotBlank(name), Entity::getName, name)
     .eq(age!=null && age >= 0, Entity::getAge, age)

这第一个参数就叫做condition. 你看, 不用反复的用if条件判断, 还可以把条件串联者写.

这么常见的功能, 且重要的设计, 没有什么篇幅, 一句话就过了, 之前没接触过的, “谁知道这些condition都是干啥的”.

同理, 以下每一条都可以展开说明一下设计思想, 为什么这么设计, 都是干啥用的. 这些一概没有. 这才是需要文档的啊, 而不是自动生成的API列表!

所以我给它的文档一个“垃圾”的评价, 是有理有据的.

二、强制的架构

MyBatis-Plus自称只是加强MyBatis, 不限制它的使用. 实际上你想用得爽, 你必须接受 MyBatis-Plus 的架构:

  1. 你只操作单表!
  2. 你的表只有一个主键!

这就是很流行的一种思想, 估计是由阿里传出来的, 什么表不能有外键啊、设计简单不关联、所有业务逻辑不能放数据库啊, 等等. 这种事情很常见. 一般做这种优化的, 都是业务场景不复杂, 但是性能要求和数据量都非常大, 于是要做这种妥协.

但是对于大部分做业务开发的来说, 完全是不同的场景, 通常是数据结构复杂, 业务流程复杂, 而用户量和数据量却有限, 很容易出现数据不一致性. 这种情况下, 完全没有必要采用以上妥协.

很多人以此为借口, 不好好学习关系数据库的精髓, 而照搬照用. 科学大牛创造了关系理论, 工程大牛们创造了关系数据库, 具有丰富的功能, 帮助你确保数据一致性, 帮助你搞定数据建模问题, 结果到了你这, 啥都不用, 把关系数据库(MySQL)当作NoSQL(MongoDB)来用!

我跟你们说一下如果你违背了这个设计前提, 会碰到什么结果. 如果你的表有left join, 你的分页很可能是无法返回正确结果的! 之所以说很可能, 如果left join的表, 没有重复的外键, 那么是OK的, 例如:

user

id, name, ...

user_info

id, user_id, org, ...

因为user_info里, 每个user_id只有一行数据, 所以当你left join到 user 表中, 获取额外信息时, 不会造成问题. 但是如果你要有这样的表:

user_roles

id, user_id, role

1, 1, "User"

2, 1, "Admin"

我们看到userroles中, 一个用户有两个角色, 如果你想通过一个查询语句返回用户列表并带出user_roles, 那么需要使用 MyBatis 的 collection 元素来自己写XML, 那么分页插件就会失效. 你查出的数量是role的数量, 而不是user的数量.

如果你有多主键的表, MP是不支持的, 你最好自定义一个额外的自增主键, 然后使用哪个主键去做增删改, 然后使用Wrapper条件去做查找. 如果你有自定义的 TypeHandler, 需要在定义实体时(TableName注解), 添加 autoResultMap = true 这个选项. 否则你的 TypeHandler 只在保存和更新时有效, 查询的时候无效. 如果你想复用 MP 自动生成的ResultMap, 需要从源码里查找那个ResultMap是怎么命名ID的. 从ID规则来看, MP是没想让用户这么用的. 也从另一个侧面证明了, MP 是一个伪装成 SpringFramework 的 SpringBoot, 它对你已经有预设了, 但是却说自己“只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑”。确实, 如果你整体的数据架构都是基于NoSQL来做的(也就是没有设计), 你会有非常爽的感受. 如果你是一个真正的关系数据库用户, 你会痛不欲生.

如果我要给 MyBatis-Plus 换个更合适的名字, 可以叫做 MyBatis-NoSQL 或者 MyBatis-MongoDB. 如果你用过 MongoDB, 又碰巧用过 MP, 你会发现这种设计哲学上的相似性. 如果你在选型, 你要好好考虑一下.

三、设计细节还有待完善

一些小细节, 影响不大, 但是还是值得一提, 比如QueryWrapper, 直接使用字符串作为key, 还可以直接设置SQL语句作为条件, 在API层面混用不同的设计(属于leaky abstraction), 这样IDE无法对字段有效性做检查, 非常容易出错. 因此我推荐使用LambdaQuery, 至少保证类型正确.

API接口欠考虑, 如果你想用多个字段排序, 接口如下:

query.orderByAsc(Entity::getNumber, Entity::getCreateTime);

这时IDE(java compiler)会给warning: Unchecked generics array creation for varargs parameter. 一个库的接口, 正确使用, 会出现warning, 这是不可接受的. 有追求的程序员, 会要求自己的代码没有warning, 更别说一个广泛使用的库了.

半吊子功能, 比如乐观锁插件, 只支持在Update时校验Version, 不支持删除时校验Version (当然逻辑删除本质上是update, 不知道是否可以透明使用这个校验). 既然声称有这个功能, 就弄得完整一点, 常见的场景不覆盖, 有点不太合适.

四、总结

当然MyBatis-Plus在其他方面有很多值得夸赞的地方, 我就不提了, 因为本篇主要是记录我遇到的坑. 可能有些苛刻, 但是我希望能帮助到一些之前没有接触过MP的朋友. 如果你的数据架构跟MP的设计思想一致, 我强烈建议你使用. 否则我建议使用MyBatis Generator来简化你的常见单表操作, 或者使用JPA (或者Spring Data JPA).

                                                                        需要更多教程,微信扫码即可

                                                                              

                                                                                         ???

                                                        别忘了扫码领资料哦【高清Java学习路线图】

                                                                     和【全套学习视频及配套资料】
 


版权声明:本文为qfzhaohan原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。