百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

最近火起的 Bean Searcher 与 MyBatis Plus 到底有啥区别?

ccwgpt 2024-12-16 11:23 77 浏览 0 评论

Bean Searcher 号称 任何复杂的查询都可以 一行代码搞定,但 Mybatis Plus 似乎也有类似的动态查询功能,它们有怎样的区别呢?

区别一(基本)

Mybatis Plus 依赖 MyBatis, 功能 CRUD 都有,而 Bean Seracher 不依赖任何 ORM,只专注高级查询。

只有使用 MyBatis 的项目才会用 Mybatis Plus,而使用 Hibernate,Data Jdbc 等其它 ORM 的人则无法使用 Mybatis Plus。但是这些项目都可以使用 Bean Searcher(可与任何 ORM 配合使用,也可单独使用)。

使用 Mybatis Plus 需要编写实体类 和 Mapper 接口,而 Bean Searcher 只需编写 实体类,无需编写任何接口。

这个区别意义其实不大,因为如果你用 Mybatis Plus,在增删改的时候还是需要定义 Mapper 接口。

区别二(高级查询)

Mybatis Plus 的 字段运算符 是静态的,而 Bean Searcher 的是动态的。

字段运算符指的是某字段参与条件时用的是 =、> 亦或是 like 这些条件类型。
不只 Mybatis Plus,一般的传统 ORM 的字段运算符都是静态的,包括 Hibernate、Spring data jdbc、JOOQ 等。

下面举例说明。对于只有三个字段的简单实体类:

public class User {
    private long id;
    private String name;
    private int age;
    // 省略 Getter Setter
}

1)使用 MyBatis Plus 查询:

依赖:

implementation 'com.baomidou:mybatis-plus-boot-starter:3.5.2

首先要写一个 Mapper 接口:

public interface UserMapper extends BaseMapper<User> {
}

然后在 Controller 里写查询接口:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserMapper userMapper;

    @GetMapping("/mp")
    public List<User> mp(User user) {
        return userMapper.selectList(new QueryWrapper<>(user));
    }
    
}

此时这个接口可以支持 三个检索参数,id, name, age,例如:

  • GET /user/mp? name=Jack 查询 name 等于 Jack 的数据
  • GET /user/mp? age=20 查询 age 等于 20 的数据

但是他们所能表达的关系都是 等于,如果你还想查询 age > 20 的数据,则无能为力了,除非在实体类的 age 字段上加上一条注解:

@TableField(condition = "%s>#{%s}")
private int age;

但加了注解后,age 就 只能 表达 大于 的关系了,不再可以表达 等于了。所以说,MyBatit Plus 的 字段运算符静态 的,不能由参数动态指定。

当然我们可以在 Controller 里增加代码让它支持,但这样代码就不只一行了,检索的需求越复杂,需要编写的代码就越多了。

2)使用 Bean Searcher 查询:

依赖:

implementation 'com.ejlchina:bean-searcher-boot-starter:3.7.0'

不用编写任何接口,复用同一个实体类,直接进行查询:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private BeanSearcher beanSearcher;

    @GetMapping("/bs")
    public List<User> bs(@RequestParam Map<String, Object> params) {
        // 你是否对入参 Map 有偏见?如果有,请耐心往下看,有方案
        return beanSearcher.searchList(User.class, params);
    }
    
}

此时这个接口可以支持的检索参数就非常多了:

  • GET /user/bs? name=Jack 查询 name 等于 Jack 的数据
  • GET /user/bs? name=Jack & name-ic=true 查询 name 等于 Jack 时 忽略大小写
  • GET /user/bs? name=Jack & name-op=ct 查询 name 包含 Jack 的数据
  • GET /user/bs? age=20 查询 age 等于 20 的数据
  • GET /user/bs? age=20 & age-op=gt 查询 age 大于 20 的数据
  • 等等...

可以看出,Bean Searcher 对每个字段使用的 运算符 都可以由参数指定,它们是 动态 的。

无论查询需求简单还是复杂,Controller 里都只需一行代码。
参数 xxx-op 可以传哪些值?参阅这里:bs.zhxu.cn/guide/lates…

看到这里,如果看的明白,应该有一半的读者开始感慨:好家伙,这不是把后端组装查询条件的过程都甩给了前端?谁用了这个框架,不会被前端打死吗?

哈哈,我是不是道出了你现在心里的想法?如果你真的如此想,请仔细回看我们正在讨论的主题:【高级查询】! 如果 不能理解什么是高级查询,我再贴个图助你思考

当然也并不是所有的检索需求都如此复杂,当前端不需要控制检索方式时,xxx-op 参数 可以省略,省略时,默认表达的是 等于,如果你想表达 其它方式,只需一个注解即可,例如:

@DbField(onlyOn = GreaterThan.class)
private int age;

这时,当前端只传一个 age 参数时,执行的 SQL 条件就是 age > ? 了,并且即使前端多传一个 age-op 参数,也不再起作用了。

这其实是条件约束,下文会继续讲到。

区别三(逻辑分组)

就上文所例的代码,除却运算符 动静 的区别,Mybatis Plus 对接收到的参数生成的条件 都是且的关系,而 Bean Searcher 默认也是且,但支持 逻辑分组。

再举例说明,假设查询条件为:

( name = Jack 并且 age = 20 ) 或者 ( age = 30 )

此时,MyBatis Plus 的一行代码就无能为力了,但 Bean Searcher 的一行代码仍然管用,只需这样传参即可:

  • GET /user/bs? a.name=Jack & a.age=20 & b.age=30 & gexpr=a|b

这里 Bean Searcher 将参数分为 a, b 两组,并用新参数 gexpr 来表达这两组之间的关系(a 或 b)。

实际传参时gexpr的值需要 URLEncode 编码一下: URLEncode('a|b') => 'a%7Cb',因为 HTTP 规定参数在 URL 上不可以出现 | 这种特殊字符。当然如果你喜欢 POST, 可以将它放在报文体里。

分组功能非常强大,但如此复杂的检索需求也确实罕见,这里不再细述,详情可阅:bs.zhxu.cn/guide/lates…

区别四(多表联查)

在不写 SQL 的情况下,Mybatis Plus 的动态查询 仅限于 单表,而 Bean Searcher 单表 和 多表 都支持的一样好。

这也是很重要的一点区别,因为 大多数高级查询 场景都是 需要联表 的。

当然有些人坚持用单表做查询,为了避免联表,从而在主表中冗余了很多字段,这不仅造成了 数据库存储空间压力急剧增加,还让项目更加难以维护。因为源数据一但变化,你必须同时更新这些冗余的字段,只要漏了一处,BUG 就跳出来了。

还是举个例子,某订单列表需要展示 订单号,订单金额,店铺名,买家名 等信息,用 Bean Searcher 实体类可以这么写:

@SearchBean(
    tables = "order o, shop s, user u",  // 三表关联
    joinCond = "o.shop_id = s.id and o.buyer_id = u.id",  // 关联关系
    autoMapTo = "o"  // 未被 @DbField 注解的字段都映射到 order 表
)
public class OrderVO {
    private long id;         // 订单ID   o.id
    private String orderNo;  // 订单号   o.order_no
    private long amount;     // 订单金额 o.amount
    @DbField("s.name")
    private String shop;     // 店铺名   s.name
    @DbField("u.name")
    private String buyer;    // 买家名   u.name
    // 省略 Getter Setter
}

有心的同学会注意到,这个实体类的命名并不是 Order, 而是 OrderVO。这里只是一个建议的命名,因为它是本质上就是一个 VO,作用只是一个视图实体类,所以建议将它和普通的单表实体类放在不同的 package 下(这只是一个规范)。

然后我们的 Controller 中仍然只需一行代码:

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private BeanSearcher beanSearcher;

    @GetMapping("/index")
    public SearchResult<OrderVO> index(@RequestParam Map<String, Object> params) {
        // search 方法同时会返回满足条件的总条数
        return beanSearcher.search(OrderVO.class, params);
    }

}

这就实现了一个支持高级查询的 订单接口,它同样支持在上文 区别二区别三 中所展示的各种检索方式。

从本例可以看出,Bean Searcher 的检索结果是 VO 对象,而非普通的单表实体类(DTO),这 省去了 DTO 向 VO 的转换过程,它可以直接返回给前端。

区别五(使用场景)

在事务性的接口用推荐使用 MyBatis Plus, 非事务的检索接口中推荐使用 Bean Searcher

例如,创建订单接口,在这个接口内部同样有很多查询,比如你需要查询 店铺的是否已经打烊,商品的库存是否还足够,这些查询场景,推荐直接使用 原有的 MyBatis Plus 就好,不必再用 Bean Seracher 了。

网友疑问

1)这貌似开放很大的检索能力,风险可控吗?

Bean Searcher 默认对实体类中的每个字段都支持了很多种检索方式,但是我们也可以对它进行约束。

条件约束

例如,User 实体类的 name 字段只允许 精确匹配 与 后模糊 查询,则在 name 字段上添加一个注解即可:

@DbField(onlyOn = {Equal.class, StartWith.class})
private String name;

再如:不允许 age 字段参与 wehere 条件,则可以:

@DbField(conditional = false)
private int age;

参考:bs.zhxu.cn/guide/lates…

排序约束

Bean Searcher 默认允许按所有字段排序,但可以在实体类里进行约束。例如,只允许按 age 字段降序排序:

@SearchBean(orderBy = "age desc", sortType = SortType.ONLY_ENTITY)
public class User {
    // ...
}

或者,禁止使用排序:

@SearchBean(sortType = SortType.ONLY_ENTITY)
public class User {
    // ...
}

参考:bs.zhxu.cn/guide/lates…

2)使用 Bean Searcher 后 Controller 的入参必须是 Map 类型?

:这 并不是必须的,只是 Bean Searcher 的检索方法接受这个类型的参数而已。如果你在 Controller 入参那里 用一个 POJO 来接收也是可以的,只需要再用一个工具类把它转换为 Map 即可,只不过 平白多写了一个类 而已,例如:

@GetMapping("/bs")
public List<User> bs(UserQuery query) {
    // 将 UserQuery 对象转换为 Map 再传入进行检索
    return beanSearcher.searchList(User.class, Utils.toMap(query));
}

这里为什么不使用 User 实体类类接收呢? 因为 Bean Searcher 默认支持很多参数,而原有的 User 实体类中的字段不够多,用它来接收的话会有很多参数无法接收。如果咱就是不想要哪些额外的参数可以直接使用 User 实体类。

这里的 UserQuery 可以这么定义:

// 继承 User 里的字段
public class UserQuery extends User {
    // 附加:排序参数
    private String order;
    private String sort;
    // 附加:分页参数
    private Integer page;
    private Integer size;
    // 附加:字段衍生参数
    private String id_op;   // 由于字段命名不能有中划线,这里有下划线替代
    private String name_op; // 前端传参的时候就不能传 name-op,而是 name_op 了
    private String name_ic;
    private String age_op;
    // 省略其它附加字段...
    
    // 省略 Getter Setter 方法
}

然后 Utils 工具类的 toMap 方法可以这样写(这个工具类是通用的):

public static Map<String, Object> toMap(Object bean) {
    Map<String, Object> map = new HashMap<>();
    Class<?> beanClass = bean.getClass();
    while (beanClass != Object.class) {
        for (Field field : beanClass.getDeclaredFields()) {
            field.setAccessible(true);
            try {
                // 将下划线转换为中划线
                Strubg name = field.getName().replace('_', '-');
                map.put(name, field.get(bean));
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
        beanClass = beanClass.getSuperclass();
    }
    return map;
}

这样就可以了,该接口依然可以支持很多种检索方式:

  • GET /user/bs? name=Jack 查询 name 等于 Jack 的数据
  • GET /user/bs? name=Jack & name_ic=true 查询 name 等于 Jack 时 忽略大小写
  • GET /user/bs? name=Jack & name_op=ct 查询 name 包含 Jack 的数据
  • GET /user/bs? age=20 查询 age 等于 20 的数据
  • GET /user/bs? age=20 & age_op=gt 查询 age 大于 20 的数据
  • 等等...

注意使用参数是 name_op,不再是 name-op 了

以上的方式应该满足了一些强迫症患者的期望,但是这样的代价是多写一个 UserQuery 类,这不禁让我们细想:这样做值得吗?

当然,写成这样是有一些好处的:

  • 便于参数校验
  • 便于生成接口文档

但是:

  • 这是一个 非事务性 的检索接口,参数校验真的那么必要吗?本来就可以无参请求,参数传错了系统自动忽略它是不是也可以?
  • 如果了解了 Bean Searcher 参数规则,是不是不用这个 UserQuery 类也可以生成文档,或者在文档中一句话概括 该接口是 Bean Searcher 检索接口,请按照规则传递参数,是不是也行呢?

所以,我的建议是:一切以真实需求为准则,不要为了规范而去规范,莫名徒增代码。

3)前端乱传参数的话,存在 SQL 注入风险吗?

答:不存在的,Bean Searcher 是一个 只读 ORM,它也存在 对象关系映射,所传参数都是实体类内定义的 Java 属性名,而非数据库表里的字段名(当前端传递实体类未定义的字段参数时,会被自动忽略)。

也可以说:检索参数与数据库表是解耦的

4)可以随意传参,会让用户获取本不该看到的数据吗?

答:不会的,因为用户 可获取数据最多的请求就是无参请求,用户尝试的任何参数,都只会缩小数据范围,不可能扩大

如果想做 数据权限,根据不同的用户返回不同的数据:可在 Controller 层向检索参数中手动添加一个参数即可,或者在 ControllerAdvise 里注入限制参数也行。

5)效率虽有提高,但性能如何呢?

前段时间又不少朋友看了这篇文章私下问我 Bean Searcher 的性能如何,这个周末我就在家做了下对比测试,结果如下:

  • 比 Spring Data Jdbc 高 5 ~ 10
  • 比 Spring Data JPA 高 2 ~ 3
  • 比 原生 MyBatis 高 1 ~ 2
  • 比 MyBatis Plus 高 2 ~ 5

完整报告:

  • github.com/ejlchina/be…
  • gitee.com/troyzhxu/be…

以上测试是基于 H2 内存数据库进行

测试源码地址,大家可自行测试对比:

  • github.com/ejlchina/be…
  • gitee.com/troyzhxu/be…

6)支持哪些数据库?

只要支持正常的 SQL 语法,都是支持的,另外 Bean Searcher 内置了四个方言实现:

  • 分页语法和 MySQL 一样的数据库,默认支持
  • 分页语法和 PostgreSql 一样的数据库,选用 PostgreSql 方言 即可
  • 分页语法和 Oracle 一样的数据库,选用 Oracle 方言 即可
  • 分页语法和 SqlServer(v2012+)一样的数据库,选用 SqlServer 方言 即可

如果分页语法独创的,则只需自定义一个方言,只需实现两个方法,参考:高级 > SQL 方言 章节。

总结

上文所述的各种区别,并不是要说 MyBatis Plus 和 Bean Searcher 那个好哪个不好,而是它们 专注的领域 确实不一样。

Bean Searcher 在刚诞生的时候是专门用来处理那种特别复杂的检索需求(如上文中的例图所示),一般都用在管理后台系统里。
但用着用着,我们发现,对检索需求没那么复杂的普通分页查询接口,Bean Searcher 也非常好用
代码写起来比用传统的 ORM 要简洁的多,只需一个实体类和 Controller 里的几行代码,Service 和 Dao 什么的全都消失了,而且它返回的结果就是 VO, 也 不需要再做进一步的转换 了,可以直接返回给前端。

在项目中配合使用它们,事务中使用 MyBatis Plus,列表检索场景使用 Bean Searcher,你将 如虎添翼

实际上,在旧项目中集成 Bean Searcher 更加容易,已有的单表实体类都能直接复用,而多表关联的 VO 对象类也只需添加相应注解即可拥有强大的检索能力。
无论项目原来 ORM 用的是 MyBatis, MP, 还是 Hibernate,Data Jdbc 等,也无论 Web 框架是 Spring Boot, Spring MVC 还是 Grails 或 Jfinal 等,只要是 java 项目, 都可以用它,为系统赋能高级查询。

最后附上 Bean Searcher 的相关连接:

  • Bean Searcher 官方文档(非常详细)
  • 我这样写代码,比直接使用 MyBatis 效率提高了 100 倍(入门体验)
  • GitHub 源码仓库(点个Star 吧)
  • Gitee 源码仓库(点个Star 吧)

如果觉得文本不错,动手点个赞吧 ^_^


作者:乔伊酱
原文链接:https://juejin.cn/post/7092411551507808264

“做程序员,圈子和学习最重要”因为有有了圈子可以让你少走弯路,扩宽人脉,扩展思路,学习他人的一些经验及学习方法!同时在这分享一下是一直以来整理的Java后端进阶笔记文档和学习资料免费分享给大家!需要资料的朋友私信我扣【06】

相关推荐

一个基于.Net Core遵循Clean Architecture原则开源架构

今天给大家推荐一个遵循CleanArchitecture原则开源架构。项目简介这是基于Asp.netCore6开发的,遵循CleanArchitecture原则,可以高效、快速地构建基于Ra...

AI写代码翻车无数次,我发现只要提前做好这3步,bug立减80%

写十万行全是bug之后终于找到方法了开发"提示词管理助手"新版本那会儿,我差点被bug整崩溃。刚开始两周,全靠AI改代码架构,结果十万行程序漏洞百出。本来以为AI说没问题就稳了,结果...

OneCode低代码平台的事件驱动设计:架构解析与实践

引言:低代码平台的事件驱动范式在现代软件开发中,事件驱动架构(EDA)已成为构建灵活、松耦合系统的核心范式。OneCode低代码平台通过创新性的注解驱动设计,将事件驱动理念深度融入平台架构,实现了业务...

国内大厂AI插件评测:根据UI图生成Vue前端代码

在IDEA中安装大厂的AI插件,打开ruoyi增强项目:yudao-ui-admin-vue31.CodeBuddy插件登录腾讯的CodeBuddy后,大模型选择deepseek-v3,输入提示语:...

AI+低代码技术揭秘(二):核心架构

本文档介绍了为VTJ低代码平台提供支持的基本架构组件,包括Engine编排层、Provider服务系统、数据模型和代码生成管道。有关UI组件库和widget系统的信息,请参阅UI...

GitDiagram用AI把代码库变成可视化架构图

这是一个名为gitdiagram的开源工具,可将GitHub仓库实时转换为交互式架构图,帮助开发者快速理解代码结构。核心功能一键可视化:替换GitHubURL中的"hub...

30天自制操作系统:第六天:代码架构整理与中断处理

1.拆开bootpack.c文件。根据设计模式将对应的功能封装成独立的文件。2.初始化pic:pic(可编程中断控制器):在设计上,cpu单独只能处理一个中断。而pic是将8个中断信号集合成一个中断...

AI写代码越帮越忙?2025年研究揭露惊人真相

近年来,AI工具如雨后春笋般涌现,许多人开始幻想程序员的未来就是“对着AI说几句话”,就能轻松写出完美的代码。然而,2025年的一项最新研究却颠覆了这一期待,揭示了一个令人意外的结果。研究邀请了16位...

一键理解开源项目:两个自动生成GitHub代码架构图与说明书工具

一、GitDiagram可以一键生成github代码仓库的架构图如果想要可视化github开源项目:https://github.com/luler/reflex_ai_fast,也可以直接把域名替换...

5分钟掌握 c# 网络通讯架构及代码示例

以下是C#网络通讯架构的核心要点及代码示例,按协议类型分类整理:一、TCP协议(可靠连接)1.同步通信//服务器端usingSystem.Net.Sockets;usingTcpListene...

从复杂到优雅:用建造者和责任链重塑代码架构

引用设计模式是软件开发中的重要工具,它为解决常见问题提供了标准化的解决方案,提高了代码的可维护性和可扩展性,提升了开发效率,促进了团队协作,提高了软件质量,并帮助开发者更好地适应需求变化。通过学习和应...

低代码开发当道,我还需要学习LangChain这些框架吗?| IT杂谈

专注LLM深度应用,关注我不迷路前两天有位兄弟问了个问题:当然我很能理解这位朋友的担忧:期望效率最大化,时间用在刀刃上,“不要重新发明轮子”嘛。铺天盖地的AI信息轰炸与概念炒作,很容易让人浮躁与迷茫。...

框架设计并不是简单粗暴地写代码,而是要先弄清逻辑

3.框架设计3.框架设计本节我们要开发一个UI框架,底层以白鹭引擎为例。框架设计的第一步并不是直接撸代码,而是先想清楚设计思想,抽象。一个一个的UI窗口是独立的吗?不是的,...

大佬用 Avalonia 框架开发的 C# 代码 IDE

AvalonStudioAvalonStudio是一个开源的跨平台的开发编辑器(IDE),AvalonStudio的目标是成为一个功能齐全,并且可以让开发者快速使用的IDE,提高开发的生产力。A...

轻量级框架Lagent 仅需20行代码即可构建自己的智能代理

站长之家(ChinaZ.com)8月30日消息:Lagent是一个专注于基于LLM模型的代理开发的轻量级框架。它的设计旨在简化和提高这种模型下代理的开发效率。LLM模型是一种强大的工具,可以...

取消回复欢迎 发表评论: