架构实战第3篇:三个文件消灭90%重复代码-泛型CRUD架构解析

发布时间:2026/9/9 19:34:56

架构实战第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样板代码上。如果觉得这篇文章对你有启发欢迎点赞、收藏、分享~
延伸阅读

更多相关文章

2026/9/9 19:34:43

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

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

2026/9/6 18:03:41

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

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

2026/9/7 23:35:13

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

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

2026/9/9 19:30:15

档案数字化加工平台搭建实战:从扫描到OCR全流程管理

档案数字化加工这事儿,圈内人都懂,看着就是个“扫描 录入”,但真干起来,扫描、修图、OCR识别、著录、质检、入库,哪个环节掉了链子,整条流水线都得堵车。早期我们是纯人肉流水线,扫描员、修图员…

2026/9/9 19:30:15

CTA趋势策略量化平台横向实测:回测、数据与实盘全维度对比

我们做CTA的人,每年最头疼的其实不是策略本身,反而是平台选型。策略逻辑写来写去就那几板斧:突破、均线、动量、波动率调节,真正拉开差距的往往是数据干不干净、撮合模型真不真实、实盘对接顺不顺畅。进入2026年,市面上…

2026/9/9 19:30:15

随机森林嵌入式特征选择:从原理到实战的完整指南

做特征选择的时候,我最常被问到的就是“随机森林不是用来分类和回归的吗?怎么还能选特征”。这个问题我当年也问过,后来真正上手用了才知道,随机森林做嵌入式特征选择,其实是机器学习里一个又简单又实用的玩法&#xf…

2026/9/9 19:30:15

从报价单到合同:2026商用健身器材团购省钱实操指南

去年底帮朋友谈工作室健身器材采购,销售传来的报价单让我差点直接关掉:30来台设备报82万,按他们给的套餐价也要76万。最后我们换了一种谈法,把整单按项目思路重新拆解,成交价57万,算是压了30%出头。这些年陆…

2026/9/9 19:25:14

云手机底层架构与实现逻辑全解析:从虚拟化到流媒体传输

这几年,云手机这个概念被念叨得越来越多。身边做移动开发测试的朋友、搞私域运营的同行、甚至一些做远程办公管理的团队,都在私下讨论能不能把手上的安卓业务“扔到云端去跑”。市面上冒出来一堆云手机平台,有的按小时卖,有的按并…

2026/9/9 13:11:35

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/8 7:15:15

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/9 16:31:09

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/9 0:00:48

MHS模型硬件标准:让大模型像调用软件一样控制物理设备

让Claude真正看着显微镜说“这个细胞形态不太对”,或者让大模型自己调一版机械臂的运动轨迹,这事儿听上去已经很接近科幻片了。但你真上手试一次就会发现,模型不缺智商,缺的是一个能插进显微镜、机械臂、激光控制器里的“通用插座…

2026/9/9 0:00:48

AI五大核心方向详解:从机器学习到大模型,零基础转行选哪条?

会有人告诉我,他想转行学AI,但打开招聘网站一看直接傻眼:机器学习、深度学习、自然语言处理、计算机视觉、大模型应用……满屏都是这些词,好像每个都会一点,又好像每个都离自己很远。还有人上来就问“学Python还是学Ja…

2026/9/9 0:00:49

从50行最小循环到生产级AI引擎:工程化改造全解析

直接说干货。这一章我写的不是那种"hello world跑通某个模型"的教程,而是把AI引擎当做一个真正要上线、要被人调用、要扛流量的系统来聊。从最初只有50行的最小循环,到能够承载生产流量的AI引擎,中间差的不是代码量,而是…

2026/9/7 16:23:03

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/7 22:46:00

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/9 10:21:54

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码