不用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);}}这里有两个细节:
_t==0时自动跳到3(修改),不需要手动设状态_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 #政务信息化 #自研框架
