当前位置: 首页 > news >正文

列表查询的 GraphQL:一行代码终结你的 if-else 地狱

一个后端工程师的自白:为什么你写了 100 行 Java 代码,其实只做了一件事——把前端传进来的几个参数,拼成一条 SQL。


一页纸的需求

产品经理小王给我发了一张原型图。很普通的后台管理页面:

  • 表格分页展示
  • 顶部四个筛选条件:姓名、年龄区间、部门、入职时间
  • 可以按任意列排序
  • 底部统计:总人数、平均年龄

"这个简单吧?下午能上线吗?"他问。

我说:“能。”

然后默默打开 IDEA,开始写代码。


如果你也用 MyBatis,接下来 30 分钟你会做什么

// 首先,你要写一条动态 SQL<selectid="searchUsers"resultType="UserVO">SELECT u.*, d.name as deptName FROM user u LEFT JOIN dept d ON u.dept_id = d.id<where><iftest="name != null and name != ''">AND u.name LIKE CONCAT('%', #{name}, '%')</if><iftest="ageMin != null">AND u.age >= #{ageMin}</if><iftest="ageMax != null">AND u.age&lt;= #{ageMax}</if><iftest="dept != null and dept != ''">AND d.name = #{dept}</if><iftest="entryDateStart != null">AND u.entry_date >= #{entryDateStart}</if><iftest="entryDateEnd != null">AND u.entry_date&lt;= #{entryDateEnd}</if></where><iftest="sortField != null and sortOrder != null">ORDER BY ${sortField} ${sortOrder}</if>LIMIT #{offset}, #{size}</select>

还没完。你还需要一个统计 SQL、一个 Controller 方法解析参数、一个 Service 层做分页计算、一个 Mapper 接口、一个 VO 类专门承接查询结果——还要小心${}SQL 注入。

需求只说了四个筛选条件,代码已经快 100 行了。

然后小王说:“对了,把部门筛选改成支持多选。还有,加一个性别筛选。”

你深吸一口气,继续写。

这不是 MyBatis 的问题——换 JPA,你也不会更轻松:

Specification<User>spec=(root,query,cb)->{List<Predicate>predicates=newArrayList<>();// 联表:要查部门名,先 JOIN department 表Join<User,Department>deptJoin=root.join("dept",JoinType.LEFT);if(StringUtils.isNotBlank(name)){predicates.add(cb.like(root.get("name"),"%"+name+"%"));}if(ageMin!=null){predicates.add(cb.ge(root.get("age"),ageMin));}if(ageMax!=null){predicates.add(cb.le(root.get("age"),ageMax));}if(StringUtils.isNotBlank(dept)){predicates.add(cb.equal(deptJoin.get("name"),dept));}// ... 同样的 if 地狱,只是换了语法returncb.and(predicates.toArray(newPredicate[0]));};// 除此之外,还有 排序、分页、总条数、字段统计,每一项都都需要你不断的堆砌代码 ...

MyBatis 在 XML 里写<if>,JPA 在 Java 里拼Join+Predicate。本质没变——你还是在用手工方式把参数翻译成查询条件。

这个问题存在的唯一原因,就是你把"声明"和"执行"混在了一起。


如果换一种思想

我们换一个视角看这个问题。

数据库表,你已经定义好了。前端要查什么,用户说了算。

那为什么中间的翻译工作——把 HTTP 参数翻译成 SQL——需要你一行一行写 if-else?

有没有可能,让一个聪明的中间层来做这件事?它知道:

  • 你的实体有哪些字段 → 这就是检索边界
  • 前端传了哪些参数 → 这就是检索意图
  • 两者一结合 → 生成 SQL

这个思路不是我的发明。在 API 领域,它有一个如雷贯耳的名字:

GraphQL— 客户端指定要什么字段,服务端返回什么字段。一次请求,替代多次 REST 调用。

那在列表查询领域,能不能有同样的东西?

维度GraphQLBean Searcher
领域API 数据查询数据库列表检索
客户端控制什么返回哪些字段返回哪些字段 + 按什么筛选 + 按什么排序 + 分页多少
协议POST + GraphQL body标准 HTTP 参数(GET/POST 均可)
核心思想声明你要什么数据声明检索边界,参数驱动查询
接入成本改 API 层、加 Schema一个依赖,零代码改造

一句话:GraphQL 让前端在一次请求中自由控制返回数据;Bean Searcher 让前端在一次请求中自由控制筛选、排序、分页和统计——用 REST 最熟悉的 URL 参数方式。


列表检索领域的 GraphQL

你只需要定义一个实体:

@SearchBean(tables="user u, dept d",where="u.dept_id = d.id",autoMapTo="u")publicclassUserVO{privateLongid;privateStringname;privateIntegerage;privateStringgender;@DbField("d.name")privateStringdeptName;privateLocalDateentryDate;// getters & setters...}

然后,整个检索接口一行代码

@GetMapping("/user/search")publicSearchResult<UserVO>search(HttpServletRequestrequest){returnbeanSearcher.search(UserVO.class,MapUtils.flat(request.getParameterMap()));}

前端直接 GET 请求:

GET /user/search?name=张&age-0=20&age-1=30&age-op=bt&sort=age&order=desc

这一行代码返回的数据长这样:

{"dataList":[{"id":1,"name":"张三","age":25,"deptName":"技术部","entryDate":"2023-03-01"},{"id":2,"name":"张小明","age":28,"deptName":"产品部","entryDate":"2022-11-15"}],"totalCount":47,"summaries":[1350]}

分页、联表、多条件筛选、排序、统计——一个接口全搞定。没有 XML。没有 if-else。没有 VO 转换代码。

这就是 Bean Searcher——一个我用了三年、忍不住想安利给所有后端工程师的框架。


它到底做了什么

让我用一句话解释它的核心原理,因为理解了这一点,你就理解了它为什么能省掉 90% 的代码:

实体类声明检索边界,HTTP 参数驱动查询逻辑。

传统方式(MyBatis / JPA)Bean Searcher
筛选条件怎么定义XML 里写<if>/ Java 里拼QueryWrapper参数名直接映射字段名
加一个新筛选条件改 XML / 改 Java 代码 → 重新编译部署前端直接传新参数,后端零改动
多表联查手写 JOIN SQL实体类声明关联关系
返回结果需要 VO 转换层SearchBean 就是 VO
安全性自己写校验防注入、防大页、防深度偏移,全部默认开启

打个比方:传统方式是"命令式"的——你告诉框架每一步怎么做。Bean Searcher 是"声明式"的——你声明"能查什么"(实体定义边界),然后前端通过参数表达"想查什么"(驱动查询逻辑)。

你不是在写查询。你是在声明检索边界


一个会被问到的问题

“这难道不会让前端传太多参数吗?”

这个问题我被问了无数次。答案是:前端传多少参数,只和产品需求的复杂度有关,和后端用的什么框架毫无关系。

如果产品只需要一个模糊搜索框,前端就只传?name=张,不需要name-opname-ic。你甚至可以零注解使用——一个单纯的 POJO,字段名默认映射为数据库列名(驼峰转下划线)。

记住一件事:Annotation 是用来约束和精细化控制的,不是必须的。单表实体什么注解都不用加,天生可搜。


它和 MyBatis/JPA 是敌人吗?

绝对不是。

MyBatis 管增删改,Bean Searcher 管列表查。各司其职,和谐共存。

就像 GraphQL 不是为了取代 REST 而生的——它只是让 API 查询更灵活。Bean Searcher 也不是为了取代 MyBatis——它只是让列表检索不再痛苦。

什么时候用
MyBatis / JPA增删改、事务性操作、复杂业务逻辑
Bean Searcher后台管理列表、数据导出、报表查询、任何"多条件动态筛选"场景
两者的关系互补,不是替代。加一个依赖就够了。

真实项目里的体验

我在三个项目里深度使用 Bean Searcher(分别是 Spring Boot 2/3/4 和 Solon 3/4),最大的感受不是"代码少了"——而是思维方式变了

以前接到列表查询需求,脑子里想的是"这个 SQL 怎么写、参数怎么拼、排序怎么处理、分页传什么对象"。

现在接到列表查询需求,脑子里想的是"这个页面需要哪些字段,它们来自哪些表,哪些字段允许前端筛选"。

你不再是一个 SQL 拼接工。你是一个领域建模者。

而当你把这种体验告诉同事时,他们的第一反应通常是:“这不就是……Java 后端版的 GraphQL?”

“对。”


试试看

如果你读到这里,发现上面说的痛点都是你每天在经历的——那你应该试一下。

不用重构项目,不用替换 ORM,不用改变任何架构。它是一个完全不侵入的框架,和 MyBatis/JPA/Spring Data JDBC 都能共存。

  • 📖 完整文档
  • 🖥 在线 Demo(零部署体验)
  • ⭐ GitHub | Gitee

如果你觉得这玩意确实解决了你的痛点,点个 Star,让更多被列表查询折磨的 Java 工程师看到它。

毕竟——你把生命花在写 if-else 上,不如花在更有价值的事情上。

http://www.jsqmd.com/news/1272294/

相关文章:

  • 智慧化工地 安全防护装备检测数据集 智慧工地安全帽口罩反光衣车辆检测数据集的权重 推理识别检测口罩佩戴 安全锥及反光衣车辆的检测
  • Linux内核自旋锁原理与实战优化指南
  • C++ Windows进程内存读写实战:从原理到实现内存修改工具
  • 襄阳市防水补漏_2026汉江沿岸城市漏水维修攻略与五大正规团队推荐 - 雨婺虹房屋维修
  • 微信聊天记录永久保存与分析实战:从数据备份到智能洞察
  • Photon-1:通过视觉学习实现界面自动化的新范式
  • 运维小白学习记——03 indoe 机制及软硬链接
  • 无锡市防水补漏_2026江南水乡梅雨季节漏水维修全攻略与正规团队推荐 - 雨婺虹房屋维修
  • Cursor入门实操流程
  • Django自定义用户模型实战指南与避坑技巧
  • Python字符串操作从入门到精通
  • 大疆全栈系统面试,ROS2节点崩溃恢复这道题比你想的复杂
  • 2026 年现阶段湖南可靠的停车场膜结构车棚销售厂家哪家权威,揭秘:你的停车场,真的需要这种膜结构车棚吗? - 企业信息推荐【官方】
  • PCB贴片打样哪家好?专业SMT加工助力电子产品快速验证
  • Blazor组件开发指南:从基础到实战
  • Thief摸鱼神器:如何优雅地在工作中找回自己的时间掌控权?
  • 从 docker 到 runC
  • Java集合框架:Map与Set核心原理与性能优化
  • 基于CNN的水面漂浮垃圾智能识别系统开发实践
  • 终极窗口置顶神器:AlwaysOnTop免费高效工具完整使用指南
  • JTAG高速数据交换:EMU0/EMU1信号硬件设计与HS-RTDX优化
  • 龙珠Z动画资源编码解析与数字修复技术指南
  • 理杏仁使用培训
  • FLAC3D岩土工程数值模拟实战与边坡稳定性分析
  • DDR3 PCB设计实战:从信号完整性到稳定布局的工程指南
  • Adobe国际认证培训课程 备考 题型
  • 3D等变几何深度学习在分子长程相互作用建模中的应用与优化
  • AI Agent核心架构解析与实战应用指南
  • OpenAI Codex技术解析:从GPT-3到智能编程助手的实战应用
  • Nginx 添加访问状态模块