架构实战第3篇:三个文件消灭90%重复代码-泛型CRUD架构解析
摘要:在Java企业级开发中单表CRUD是最常见也是最枯燥的代码。每新增一个业务实体就要写一套几乎相同的Controller、Service——结构雷同却又不得不写。《鹿鲸项目管理工具》仅用三个文件——IOneEntityService接口、OneEntityServiceImpl抽象类、OneEntityController控制器基类——构建了一套泛型模板让所有表新增改的业务实体共享同一套CRUD骨架新增简单实体只需声明泛型Controller零代码。本文深入解析这三个文件的设计细节与背后的工程思考。一、三个文件的全貌先看三个文件各自的角色定位文件层次泛型数量核心职责IOneEntityServiceService接口5个定义DTO进VO出的业务契约OneEntityServiceImplService实现6个继承MyBatis-Flex实现通用逻辑OneEntityControllerController5个定义8个RESTful端点它们通过相同的泛型参数串联成一条完整的CRUD链路从HTTP请求到数据库操作类型安全一以贯之。二、逐文件深度解析2.1 IOneEntityServiceService接口——定义业务契约public interface IOneEntityServicePO, VO, SearchDTO, AddDTO, UpdateDTO extends IServicePO { VO getById(String id); boolean insertInfo(AddDTO addDTO); boolean updateInfo(UpdateDTO updateDTO); ListVO getList(SearchDTO dto); PageVO getPage(SearchDTO dto, PageDTO pageDTO); int getNextSortNo(String sortExpress); }5个泛型参数每个都对应一种对象类型泛型角色说明PO持久化对象数据库表映射Service内部使用VO视图对象返回给前端的展示数据SearchDTO查询参数列表/分页查询的筛选条件AddDTO新增参数前端提交的新增数据UpdateDTO修改参数前端提交的修改数据关键设计决策所有方法入参都是DTO所有返回值都是VOPO从不出现在接口签名中。这意味着PO被完全封装在Service内部。Controller层拿不到PO前端更拿不到PO。从接口契约层面就杜绝了数据库结构泄露的安全隐患。继承IServicePO这是 MyBatis-Flex 提供的基础服务接口自带save()、updateById()、removeById()、removeByIds()、list()、getById()等方法。继承它之后IOneEntityService不需要重复定义删除方法——Controller直接调用removeById和removeByIds即可。6个方法分工方法入参返回值用途getByIdString idVO主键查询单条insertInfoAddDTOboolean新增updateInfoUpdateDTOboolean修改getListSearchDTOListVO列表查询getPageSearchDTO PageDTOPageVO分页查询getNextSortNoStringint获取下一个排序号注意没有delete方法。删除操作直接使用IService继承来的removeById和removeByIds因为删除只需要ID不需要额外定义。2.2 OneEntityServiceImpl抽象实现——通用逻辑上提public abstract class OneEntityServiceImplM extends BaseMapperPO, PO, VO, SearchDTO, AddDTO, UpdateDTO extends ServiceImplM, PO implements IOneEntityServicePO, VO, SearchDTO, AddDTO, UpdateDTO { Override public int getNextSortNo(String sortExpress) { QueryWrapper queryWrapper new QueryWrapper(); queryWrapper.select(QueryMethods.max(”sort_no”).as(”sort_no”)); PO t this.getMapper().selectOneByQuery(queryWrapper); JSONObject jsonObject JSONObject.parseObject(JSONObject.toJSONString(t)); return jsonObject.getInteger(”sortNo”); } }比接口多了一个泛型参数MMapper类型约束为M extends BaseMapperPO。这个参数用于继承 MyBatis-Flex 的ServiceImplM, PO从而自动获得Mapper注入和基础数据库操作能力。为什么只实现了getNextSortNo一个方法这是整个设计最精妙的地方。6个接口方法中方法能否通用实现原因getNextSortNo✅ 可以所有实体都是查max(sort_no)逻辑完全相同getById❌ 不能每个实体需要查询的字段不同如User要排除密码insertInfo❌ 不能每个实体的业务逻辑不同如User要设置默认密码updateInfo❌ 不能每个实体的修改逻辑不同getList❌ 不能每个实体的搜索条件不同getPage❌ 不能同getListgetNextSortNo的实现逻辑查询当前表sort_no字段的最大值。这个操作对所有继承MasterPO含sortNo字段的实体都完全相同所以放在基类实现一次45个子类共享。声明为abstract class强制子类必须实现其余5个方法。编译期就能发现遗漏而不是等到运行时抛出异常。继承ServiceImplM, PO的收益——子类可以直接调用this.save(po); // 新增 this.updateById(po); // 修改 this.removeById(id); // 删除 this.removeByIds(idList); // 批量删除 queryChain().select(...) // 链式查询2.3 OneEntityController控制器基类——8个REST端点public abstract class OneEntityControllerPO, VO, SearchDTO, AddDTO extends InsertDTO, ModifyDTO extends UpdateDTO { Autowired protected IOneEntityServicePO, VO, SearchDTO, AddDTO, ModifyDTO oneEntityService; GetMapping(”/{id}”) public VO getById(PathVariable String id) { ... } PutMapping public VO updateInfo(RequestBody Validated ModifyDTO dto) { ... } PostMapping public VO insertInfo(RequestBody Validated AddDTO dto) { ... } DeleteMapping(”/{id}”) public boolean deleteById(PathVariable String id) { ... } DeleteMapping(”/list”) public boolean deleteByIdList(RequestBody ListString idList) { ... } GetMapping(”/list”) public ListVO getList(Valid SearchDTO dto) { ... } GetMapping(”/page/list”) public PageVO getPage(Valid SearchDTO dto, PageDTO pageDTO) { ... } GetMapping(”/next/sort”) public int getNextSort(RequestParam String sortExpress) { ... } }泛型参数有边界约束AddDTO extends InsertDTO, ModifyDTO extends UpdateDTO这两个约束不是摆设它们确保了AddDTO必须继承InsertDTO从而拥有getId()方法自动生成NanoIDModifyDTO必须继承UpdateDTO从而拥有id字段修改必须指定ID8个REST端点全景HTTP方法路径方法名返回值说明GET/{id}getByIdVO查询单条POST/insertInfoVO新增返回完整数据PUT/updateInfoVO修改返回更新后数据DELETE/{id}deleteByIdboolean删除单条DELETE/listdeleteByIdListboolean批量删除GET/listgetListListVO列表查询GET/page/listgetPagePageVO分页查询GET/next/sortgetNextSortint获取下一个排序号三个值得注意的设计细节细节一新增和修改都返回VOPostMapping public VO insertInfo(RequestBody Validated AddDTO dto) { this.oneEntityService.insertInfo(dto); // 先保存 return this.oneEntityService.getById(dto.getId()); // 再查询返回 }不是返回boolean而是返回保存后的完整VO。这样前端新增成功后直接拿到完整数据含系统自动填充的创建时间、ID等不需要再发一次查询请求。dto.getId()能正常工作是因为InsertDTO的getId()方法会自动生成 NanoID。细节二删除方法不需要定义在IOneEntityService中DeleteMapping(”/{id}”) public boolean deleteById(PathVariable String id) { return this.oneEntityService.removeById(id); // 直接调用IService的方法 }删除只需要ID不需要DTO转换直接使用IService继承来的removeById和removeByIds。细节三注解驱动的横切关注点PutMapping ResultBodySuccessMsg(msg ”修改数据成功”) // 统一成功提示 OperationLog(operationType OperationTypeEnum.UPDATE, targetKey ”update”) // 操作日志 Operation(summary ”修改数据信息”) // Swagger文档 public VO updateInfo(RequestBody Validated ModifyDTO dto) { ... }三个注解分别处理统一响应消息、操作日志记录、API文档生成。子类继承后自动获得这些能力不需要每个Controller重复添加。三、DTO基类体系泛型边界的设计基础Controller的泛型约束AddDTO extends InsertDTO, ModifyDTO extends UpdateDTO背后是一套精心设计的DTO基类体系InsertDTO——新增DTO基类public class InsertDTO { private String id; public String getId() { if (StringUtils.isEmpty(id)) { this.id IdUtil.nanoId(); // 自动生成NanoID } return this.id; } }​​​​​​​getId()时自动生成NanoID这就是Controller中insertInfo能在保存后用dto.getId()查询的原因——ID在DTO层面就已经确定了。UpdateDTO——修改DTO基类public class UpdateDTO { private String id; // 修改必须指定ID }SearchDTO——查询DTO基类public class SearchDTO { private String keyword; // 通用关键词搜索 }​​​​​​​所有查询DTO都自带keyword字段子类可以扩展更多筛选条件。PageDTO——分页参数public class PageDTO { private Integer pageSize; private Integer currentPage; public E PageE toPage() { return Page.of(this.currentPage, this.getPageSize()); } }toPage()方法直接转换为 MyBatis-Flex 的Page对象Controller中的getPage方法直接使用。这套基类体系是泛型约束的根基——正是因为InsertDTO提供了getId()方法Controller才敢在insertInfo中调用dto.getId()来返回新增后的数据。四、三个文件如何串联完整链路追踪以新增用户为例追踪一次请求的完整流转HTTP POST /userBody: {”userName”:”张三”,”loginCode”:”zhangsan”}│▼┌─────────────────────────────────────────────────────────┐│ OneEntityController.insertInfo(AddDTO dto) ││ AddDTO AddUserDTO (extends InsertDTO) ││ ├── Validated 触发参数校验 ││ ├── dto.getId() → InsertDTO自动生成NanoID ││ ├── 调用 oneEntityService.insertInfo(dto) ││ │ │ ││ │ ▼ ││ │ UserServiceImpl.insertInfo(AddUserDTO dto) ││ │ ├── 业务逻辑设置默认密码、有效标志 ││ │ ├── userConverter.toPO(dto) → UserPO ││ │ └── this.save(userPO) → ServiceImpl.save() ││ │ └── UserMapper.insert(userPO) → 写入数据库 ││ │ ││ ├── 调用 oneEntityService.getById(dto.getId()) ││ │ │ ││ │ ▼ ││ │ UserServiceImpl.getById(String id) ││ │ └── queryChain().where(ID.eq(id)).oneAs(UserVO.class)││ │ ││ └── return UserVO (不含密码等敏感字段) ││ │ │▼HTTP 200Body: {”id”:”V1StGXR8_Z5”,”userName”:”张三”,”loginCode”:”zhangsan”,”validFlag”:1,”createdTime”:”2026-06-17T10:30:00”,...}类型安全贯穿全链路Controller接收的AddUserDTO由泛型AddDTO约束Service的insertInfo(AddUserDTO)由接口泛型约束返回的UserVO由泛型VO约束全程不需要强制类型转换编译期保证类型正确五、设计模式解析这套设计融合了三种经典模式1. 模板方法模式OneEntityServiceImpl是模板类getNextSortNo()—— 已实现的具体方法所有子类共享getById()、insertInfo()等 —— 抽象方法子类按需实现子类在不改变整体结构的前提下重新定义某些步骤。2. 泛型参数化5~6个泛型参数将操作什么类型这个问题参数化。所有Service的结构完全一致只是类型不同。编译器在编译期检查类型匹配比运行时反射更安全、更高效。3. 依赖注入 控制反转Controller通过Autowired注入IOneEntityServiceSpring根据泛型类型自动找到对应的Service实现。子类不需要关心Controller层的存在只需要实现业务逻辑。六、实战效果三个层次的子类《鹿鲸项目管理工具》中45个Service继承OneEntityServiceImpl按复杂度分三个层次层次一零代码ControllerBusinessDomainController——只需声明泛型自动获得8个REST端点RestController RequestMapping(value ”/modeling/business/domain”) public class BusinessDomainController extends OneEntityController BusinessDomainPO, BusinessDomainVO, SearchBusinessDomainDTO, AddBusinessDomainDTO, ModifyBusinessDomainDTO { // 空的8个REST接口全部由基类提供 }层次二标准实现ProjectServiceImpl——覆写CRUD方法定制查询字段和搜索条件Override public ProjectVO getById(String id) { return this.getColumnQueryChain() .where(PROJECT_PO.ID.eq(id)) .oneAs(ProjectVO.class); } Override public ListProjectVO getList(SearchProjectDTO searchProjectDTO) { return getListQueryChain(searchProjectDTO).listAs(ProjectVO.class); } // 私有方法指定查询字段 private QueryChainProjectPO getColumnQueryChain() { return queryChain().select( PROJECT_PO.ID, PROJECT_PO.PROJECT_NAME, PROJECT_PO.PRINCIPAL_ID, // ...指定需要的字段 PROJECT_PO.CREATED_TIME, PROJECT_PO.MODIFIED_TIME ).from(PROJECT_PO); }层次三复杂业务逻辑UserServiceImpl——在标准实现基础上加入业务逻辑Override public boolean insertInfo(AddUserDTO saveUserDTO) { // 业务逻辑设置系统默认密码SHA512加密 Object defaultPassword parameterService.getParameterValue(USER_DEFAULT_PASSWORD); saveUserDTO.setLoginPassword(SaSecureUtil.sha512(defaultPassword.toString())); saveUserDTO.setValidFlag(ValidFlagEnum.VALID.getValue()); // MapStruct转换DTO→PO后持久化 UserPO userPO userConverter.toPO(saveUserDTO); return this.save(userPO); }三个层次的代码量对比层次Controller代码Service代码适用场景零代码0行0行简单配置表标准实现0行~80行普通业务表复杂逻辑0行~110行核心业务表无论哪个层次Controller永远是0行业务代码——这就是泛型模板的威力。七、设计权衡与局限性为什么不把所有CRUD逻辑都在基类实现因为每个实体的查询字段和业务逻辑不同。强行通用化要么用反射性能差、类型不安全要么用默认实现不符合大多数场景。声明为抽象方法强制子类思考每个操作的正确实现比提供一个错误的默认实现更好。为什么用6个泛型参数而不是更少每个操作的数据结构不同是现实需求。新增不需要ID和修改时间修改必须有ID但不需要创建时间查询只需要筛选条件。如果用同一个DTO会导致前端能传入不该传的字段安全漏洞和字段冗余传输。6个泛型参数确保每个操作的数据结构都是精确的。局限性只适用于单表操作多表关联查询仍需在子类手写子类仍有部分样板代码getColumnQueryChain、dtoToPO等私有方法仍有重复泛型参数较多6个参数在阅读时有一定认知负担不适用于非CRUD场景批量导入导出、复杂报表等需单独设计八、总结《鹿鲸项目管理工具》用三个文件构建了一套完整的泛型CRUD架构文件核心贡献IOneEntityService定义DTO进VO出契约PO不外泄OneEntityServiceImpl通用逻辑getNextSortNo上提特化逻辑下沉OneEntityController8个REST端点注解驱动横切关注点配合InsertDTO、UpdateDTO、SearchDTO、PageDTO四个DTO基类这套设计实现了类型安全泛型约束贯穿Controller→Service→DAO全链路安全封装PO永不外泄DTO按操作类型精确裁剪字段代码复用整个项目所有表增删改的实体共享同一套骨架简单实体零代码渐进式定制从零代码到复杂业务逻辑子类按需选择覆写粒度横切关注点统一参数校验、操作日志、统一响应、API文档由注解自动处理核心设计思想用编译期类型约束替代运行时检查用继承复用替代复制粘贴让开发者把精力集中在业务差异上而不是重复的CRUD样板代码上。如果觉得这篇文章对你有启发欢迎点赞、收藏、分享~

相关新闻

大学生如何零基础掌握PLC自动化技术?

大学生如何零基础掌握PLC自动化技术?

1. 为什么大学生应该学习PLC自动化技术?工业4.0时代,PLC(可编程逻辑控制器)作为工业自动化的大脑,正在重塑制造业的底层逻辑。对于自动化相关专业的大学生而言,掌握PLC技术不再是加分项,而是必备…

2026/7/23 5:11:20阅读更多 →
Python实现Playfair密码解密:从古典密码原理到实战脚本开发

Python实现Playfair密码解密:从古典密码原理到实战脚本开发

1. 项目概述:为什么选择Playfair密码? 如果你对古典密码学感兴趣,或者正在学习Python并想找一个兼具趣味性和挑战性的实战项目,那么编写一个Playfair密码的解密脚本绝对是个绝佳的选择。Playfair密码,也称为Playfair S…

2026/7/23 5:11:20阅读更多 →
提示工程架构师技术栈与AI系统设计实战

提示工程架构师技术栈与AI系统设计实战

1. 提示工程架构师的角色定位与技术栈剖析在AI应用爆发式增长的当下,提示工程架构师(Prompt Engineering Architect)已成为人机交互领域的关键角色。这个岗位远不止是简单编写提示词,而是需要构建完整的AI交互体系。我接触过的医疗…

2026/7/23 5:11:20阅读更多 →
0 基础入门React Native鸿蒙跨平台开发:体重单位转换器功能实战

0 基础入门React Native鸿蒙跨平台开发:体重单位转换器功能实战

本文是基于HarmonyOS API 24的进行的ReactNative 鸿蒙跨平台开发依托适配鸿蒙的 RN 运行层,使用 React 与 JS 编写一套业务代码,无需大量 ArkTS 原生开发,通用业务实现代码复用,支持按需扩展原生桥调用鸿蒙特有能力,有…

2026/7/23 19:27:12阅读更多 →
Python毕设项目:基于 Python 的智能期货模拟交易分析系统 虚拟资金期货交易演练管理平台 (源码+文档,讲解、调试运行,定制等)

Python毕设项目:基于 Python 的智能期货模拟交易分析系统 虚拟资金期货交易演练管理平台 (源码+文档,讲解、调试运行,定制等)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/7/23 19:27:12阅读更多 →
不容错过的思维导图——XMind2025(全功能版)

不容错过的思维导图——XMind2025(全功能版)

(获取方式在文末)小伙伴们大家是否接触过思维导图软件呢,如果有那一定听说过XMind吧,今天我来为大家介绍一下,一起来看一下吧。软件介绍一、AI 核心功能革新一句话总结:写 PPT 前 10 分钟就能画出一张思维导…

2026/7/23 19:27:12阅读更多 →
2026大模型智能体(Agent)面试全攻略与架构解析

2026大模型智能体(Agent)面试全攻略与架构解析

1. 2026大模型智能体(Agent)面试全攻略:从入门到精通作为一名在大模型和智能体领域摸爬滚打多年的从业者,我见证了从早期简单的聊天机器人到如今具备复杂推理能力的智能体Agent的演进过程。2026年的大模型智能体面试已经形成了一套完整的考察体系&#x…

2026/7/23 19:27:12阅读更多 →
算法第一题:两数之和(第一次接触)

算法第一题:两数之和(第一次接触)

这个方法时间复杂度为O(n) webjs

2026/7/23 19:27:12阅读更多 →
Llama2架构解析与工程实践优化指南

Llama2架构解析与工程实践优化指南

## 1. Llama2架构全景解析作为Meta开源的下一代大语言模型,Llama2在模型结构上延续了Transformer解码器的经典设计,但在细节层面进行了多项关键优化。与第一代Llama相比,Llama2系列包含70亿、130亿和700亿三种参数规格,其中Llama2…

2026/7/23 19:25:12阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/23 0:56:31阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/23 0:56:31阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/23 0:56:31阅读更多 →
Chitchatter完整指南:免费开源的终极点对点安全聊天工具

Chitchatter完整指南:免费开源的终极点对点安全聊天工具

Chitchatter完整指南:免费开源的终极点对点安全聊天工具 【免费下载链接】chitchatter Secure peer-to-peer chat that is serverless, decentralized, and ephemeral 项目地址: https://gitcode.com/gh_mirrors/ch/chitchatter Chitchatter是一款革命性的安…

2026/7/23 0:00:28阅读更多 →
从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表)

从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表)

更多请点击: https://intelliparadigm.com 第一章:从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表) 当AI副业主理人不再仅满足于单次服务交付,而是主动构建可复用、可裂变、可…

2026/7/23 0:00:28阅读更多 →
油泥处理设备哪里能买到

油泥处理设备哪里能买到

油泥处理设备哪里有?这是许多从事油田、炼化、清罐业务的从业者最关心的问题。根据河南三丰环保设备有限公司的行业经验,选购油泥处理设备的核心在于设备能否适配当地环保法规与原料特性,而非单纯看价格。该公司总经理王钦田先生指出&#xf…

2026/7/23 0:00:28阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/22 22:56:18阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/23 18:58:18阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/23 18:58:18阅读更多 →