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

不用Hibernate,自己搓ActiveRecord:状态机追踪字段变更,一个save搞定增删改

不用Hibernate,自己搓ActiveRecord:状态机追踪字段变更,一个save()搞定增删改

非科班野生程序员,深耕政务信息化20年。从VC到PB再到Java,自研框架browise也打磨了十几年。最近整理框架代码,发现不少有趣的决策,写出来和大家聊聊。最后感谢豆包、智谱、OpenCode,决策是我做的,代码是我搓的,文字是他们总结的。

问题是什么

政务系统里,前端编辑一条数据,可能是新增、可能是修改、可能是删除。如果每个操作都单独写一套逻辑,代码量爆炸。特别是批量操作的场景——一个表单里,有的行是新增的,有的行是改过的,有的行被删了。

Hibernate 太重了,我需要一个轻量的 ActiveRecord 模式:每个 Dao 对象知道自己是什么状态,调一个save()就能自动路由到 insert/update/delete。

核心设计:_t状态机

privateint_t=0;/* 0=不变,1=新增,3=修改,4=删除 *//* 修改前的值 */HashMap<String,String>_o=newHashMap<String,String>();

就这么两个字段,驱动整个状态追踪:

  • _t=0:刚查出来的数据,没变过
  • _t=1:前端标记为新增
  • _t=3:字段被SetItemValue修改过(第一次赋值时自动从0跳到3)
  • _t=4:前端标记为删除

_o这个 HashMap 存的是修改前的值——哪个字段改了,改之前是什么值,都记在这里。

save() 的自动路由

publicvoidsave()throwsutilException{if(this.isInsert())insert();elseif(this.isModefiy())update();elseif(this.isDelete())delete();}

三个判断方法:

publicbooleanisModefiy(){return_t==3;}publicbooleanisInsert(){return_t==1;}publicbooleanisDelete(){return_t==4;}

前端传 JSON 过来,fromJson()解析的时候自动读取_t值。批量数据里每一行有自己的_t,调一次newDataStore.save()遍历所有行,每行自动路由到对应操作。

insert/update/delete 怎么找 Mapper

关键在泛型反射:

publicvoidinsert()throwsutilException{this.verification();this.check();ParameterizedTypetype=(ParameterizedType)this.getClass().getGenericSuperclass();Class<?>clazz=(Class<?>)type.getActualTypeArguments()[0];DBUtil.SaveDao(clazz,getInsertMethod(),this);}

BaseDao 的泛型参数T是 MyBatis 的 Mapper 接口类型。运行时通过反射拿到实际类型,直接调DBUtil.SaveDao。子类只要这样写:

publicclassMyBusinessDaoextendsBaseDao<MyBusinessMapper>{// 不用重写 insert/update/delete/save}

SetItemValue — 触发状态变更

publicvoidSetItemValue(Stringkey,Objectvalue)throws...{if(_t==0){_t=3;// 自动标记为修改}if(_t==2)// 修改保存老值{if(_o.get(key)==null){_o.put(key,getItemStringValue(key));}}Fieldfiled=null;try{filed=getClass().getDeclaredField(key);}catch(Exceptione){filed=null;}if(filed!=null){filed.setAccessible(true);Classtype=filed.getType();value=TypeUtil.parseObject(type,value);filed.set(this,value);filed.setAccessible(false);}}

这里有两个细节:

  1. _t==0时自动跳到3(修改),不需要手动设状态
  2. _t==2时会把旧值存到_o里,用于变更审计

@ver 注解验证

保存之前自动校验字段:

// ver.java — 注解定义@Target({ElementType.FIELD,ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)public@interfacever{Stringreg()default"";booleanrequire()defaultfalse;}

在 Dao 的字段上标注:

@ver(require=true)privateStringxm;// 姓名必填@ver(reg="^\\d{18}$")privateStringsfzh;// 身份证号正则校验

verification()方法遍历所有字段:

publicbooleanverification()throwsutilException{List<Field>fieldList=newArrayList<Field>();ListProperty.listAllFields(this.getClass(),fieldList,ClassType.SELF_CLASS);for(Fieldfield:fieldList){if(field.isAnnotationPresent(ver.class)){ver ann=field.getAnnotation(ver.class);Stringvalue=String.valueOf(field.get(this));if(ann.require()){if(value==null||"null".equals(value)||"".equals(value)){thrownewutilException("["+field.getName()+"]为空!",-1);}}if(!(value==null||"null".equals(value)||"".equals(value))){Patternp=Pattern.compile(ann.reg());Matcherm=p.matcher(value);if(!m.matches()){thrownewutilException("["+field.getName()+"]不符合要求!",-1);}}}}returntrue;}

空值校验和正则校验都支持。校验失败直接抛异常,前端收到错误提示。

另外还有个check()空方法,子类可以重载做业务级校验:

publicvoidcheck()throwsutilException{// 子类重载}

toJson / fromJson — 前后端序列化

toJson()把 Dao 转成 JSON 字符串传给前端,fromJson()把前端传回的 JSON 解析成 Dao 对象。注意getBlock()方法过滤掉内部字段:

publicStringgetBlock(){returnsuper.getBlock()+"insertMethod|deleteMethod|updateMethod|deleteMethod|searchMethod|readOnly|_o|_t|";}

_o(修改前值)和_t(状态标记)在toJson()时不传给前端,但fromJson()解析时能接收前端传回的_t值。

search — 查询也封装了

publicList<?>search()throwsutilException{ParameterizedTypetype=(ParameterizedType)this.getClass().getGenericSuperclass();Class<?>clazz=(Class<?>)type.getActualTypeArguments()[0];Class<?>[]plist=this.getParameterTypes(clazz,this.getSearchMethod());if(plist!=null&&plist.length>0){if(this.getMaxRow()>0&&this.getMinRow()>=0){RowBoundspage=newRowBounds(this.getMinRow(),this.getMaxRow()-this.getMinRow());list=DBUtil.getDao(clazz,getSearchMethod(),this,page);}else{list=DBUtil.getDao(clazz,getSearchMethod(),this);}}else{list=DBUtil.getDao(clazz,getSearchMethod());}returnlist;}

查询条件就是 Dao 自己的字段值,分页也内置了。子类可以改searchMethod来指定不同的查询 SQL。

小结

BaseDao 600行代码,实现了一套完整的 ActiveRecord 模式:

  • _t状态机追踪每行数据的新增/修改/删除
  • _oHashMap 保存修改前的值
  • save()自动路由到 insert/update/delete
  • @ver注解做字段级验证
  • 泛型反射自动找到对应的 MyBatis Mapper
  • toJson/fromJson 处理前后端序列化

没有 Hibernate 的 Session 管理,没有脏检查,没有延迟加载。就是朴素的反射 + 状态标记。但在政务系统里,够用、可控、好排查。


ActiveRecord模式大家都在用,你们是自己实现的还是用的框架?评论区聊聊。

标签:#Java #ActiveRecord #自研ORM #状态机 #MyBatis #政务信息化 #自研框架

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

相关文章:

  • Fish Speech 1.5开发者案例:集成至微信小程序实现语音播报功能
  • MT5文本增强镜像实操手册:3步完成Streamlit本地部署+中文句子裂变
  • 一些硬件相关的题目
  • Retinaface+CurricularFace镜像作品集:高清人脸比对效果展示
  • JCMsuite应用:孤立线栅
  • Z-Image-Turbo-rinaiqiao-huiyewunv技术深挖:text_encoder/vae权重忽略策略对生成稳定性影响
  • 【说明书】XD-LY8话务员蓝牙耳机
  • YOLOv5-Lite架构设计:ShuffleNetV2、PPLcNet、RepVGG三大骨干网络详解
  • Kaggle 竞赛解决方案终极指南:快速掌握数据科学实战技巧
  • Blender 3MF插件:从建模到3D打印的终极桥梁
  • 在只有CPU的云服务器上,我是如何一步步让vLLM成功识别并运行Qwen2-7B的
  • 【算法题攻略】滑动窗口
  • 千问3.5-9B辅助MySQL数据库设计与优化实战
  • SpringCloud进阶--Seata与分布式事务垂
  • Z-Image-Turbo-rinaiqiao-huiyewunv 多 GPU 并行计算配置与负载均衡
  • 如何从零开始训练BAGEL多模态模型:完整实战指南
  • 【C++程序设计第7课--继承】
  • 忙得上天入地的导师派师姐助我毕设之救我狗命笔记(一)
  • 千问3.5-2B Java面试题智能辅导:刷题与知识点解析
  • 手把手教你用BERT+HanLP搞定中文社交媒体仇恨言论识别(附完整代码与数据集)
  • 忍者像素绘卷在社区运营中的应用:粉丝定制像素头像活动案例
  • Chrome文本替换插件终极指南:如何智能编辑任何网页内容
  • 忍者像素绘卷:天界画坊在软件测试中的应用:自动化生成测试用例图示
  • 智慧城市顶层设计与底层对接(上篇):战略规划与总体架构实操
  • 【基于文本的运动生成text-to-motion】Hi-Motion: Hierarchical Intention Guided Conditional Motion Synthesis
  • 基于FunASR的智能语音助手搭建:WebUI界面操作,支持实时对话
  • AI Agent vs 区块链:哪个才是真正的风口
  • 使用CNN增强cv_resnet50_face-reconstruction的边缘细节处理
  • Leather Dress Collection 与Visio结合:从文本描述自动生成系统架构图
  • 智能垃圾桶项目避坑指南:STM32驱动舵机、语音模块的那些‘坑’与解决方案