news 2026/5/4 23:02:51

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
不用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 #政务信息化 #自研框架

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 11:12:00

Z-Image-GGUF自动化测试实践:构建模型服务的软件测试流水线

Z-Image-GGUF自动化测试实践&#xff1a;构建模型服务的软件测试流水线 最近在折腾一个基于Z-Image-GGUF的图像生成服务&#xff0c;随着功能迭代越来越快&#xff0c;每次更新都提心吊胆&#xff0c;生怕新功能没做好&#xff0c;还把老功能搞坏了。手动点点点测试&#xff0…

作者头像 李华
网站建设 2026/4/18 0:40:02

别再死记硬背公式了!用Matlab/Simulink手把手复现SVPWM算法(附模型文件)

从零构建SVPWM算法&#xff1a;用Simulink可视化理解电机控制核心 电力电子工程师的日常工作中&#xff0c;最令人头疼的莫过于面对满屏的数学公式却不知如何转化为实际控制系统。记得我第一次接触SVPWM算法时&#xff0c;那些扇区判断、矢量作用时间的计算公式就像天书一样&am…

作者头像 李华
网站建设 2026/4/19 19:29:59

406记录

栈&#xff08;Stack&#xff09;是限定仅在表尾进行插入或删除操作的线性表。因此&#xff0c;对栈来说&#xff0c;表尾端有其特殊含义&#xff0c;称为栈顶&#xff08;top&#xff09;&#xff0c;相应地&#xff0c;表头端称为栈底&#xff08;bottom&#xff09;。不含元…

作者头像 李华
网站建设 2026/4/17 20:37:14

02-高并发读架构详解

高并发读架构详解 一、知识概述 高并发读是互联网应用最常见的性能挑战,典型场景包括新闻资讯、商品详情、社交动态等。核心目标是用最小的成本支持最大的读流量。 核心指标: QPS:1万 - 100万+ 响应时间:P99 < 50ms 成本控制:单请求成本 < 0.001元 典型特征: 读…

作者头像 李华