ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

17-Row脏标记核心设计

17-Row脏标记核心设计 17-Row._t/_o脏标记的核心设计browise数据协议的最小单元是Row——一个Map装数据、一个Map装旧值、一个枚举装状态。177行代码_t/_o两个协议字段的所有语义都在这里。这篇逐行拆setItemValue的状态机分支和original的首次记录不覆盖规则。文章目录17-Row._t/_o脏标记的核心设计一、三个字段撑起一个数据结构二、setItemValue11行的状态机三、为什么不用上一次值实现undo栈四、三个读方法的语义分层五、getMap返回引用不是副本六、toRaw与序列化的边界源码browise-data/src/main/java/com/browise/data/Row.java177行一、三个字段撑起一个数据结构publicclassRow{privateRowStatusstatusRowStatus.NONE;// _tprivatefinalMapString,ObjectoriginalnewLinkedHashMap();// _oprivatefinalMapString,ObjectdatanewLinkedHashMap();// 业务字段}RowStatus枚举四个值NONE0// 未修改——查询加载后的默认状态INSERT1// 新增——add()创建的行UPDATE3// 修改——NONE行被改字段后自动转DELETE4// 删除——被remove()移入delete缓冲的行跳过2不是疏忽——wiserise时代2曾表示已存在的新行browise简化后保留编号间隔兼容旧协议二、setItemValue11行的状态机publicvoidsetItemValue(Stringkey,Objectvalue){if(statusRowStatus.NONE){// 首次修改该字段时保存原始值if(!original.containsKey(key)data.containsKey(key)){original.put(key,data.get(key));}statusRowStatus.UPDATE;// NONE → UPDATE}data.put(key,value);}四个分支行为当前状态original处理status处理NONE首改字段存旧值→ UPDATEUPDATE不再记录保持最早旧值不变INSERT不记录新行没有旧值概念不变DELETE不记录不变两个精妙点①首次记录不覆盖——RowrownewRow(Map.of(name,张三));row.setItemValue(name,李四);// original{name:张三}, data{name:李四}row.setItemValue(name,王五);// original仍是{张三}data{王五}original存的是最初值不是上一次值。为什么——rejectChanges要恢复到查询加载时的样子不是用户改了几手后的样子。_o是回滚锚点锚点必须钉在最初。②INSERT行不记original——新add的行字段值都是用户刚填的旧值毫无意义。rejectChanges对INSERT行的处理是整行丢弃第18篇——没有字段级恢复的需求。三、为什么不用上一次值实现undo栈一个自然的疑问original只存一份如果想要多步撤销undo/redo呢刻意不做。编辑场景的回滚语义是放弃修改恢复原样——一步到位。多步撤销是编辑器功能Word/CAD不是数据集功能。政务表单的用户诉求是改错了恢复——恢复到加载时刻就够了。如果真要多步在Row外面包一层历史栈即可——协议保持最小扩展在外层。这是_t/_o能稳定14年的另一面不往协议里塞可推导的东西。四、三个读方法的语义分层publicObjectgetItemValue(Stringkey)// 当前值——渲染用publicObjectgetOriginal(Stringkey)// 最初值——审计/对比用publicbooleanisDirty()// status ! NONE——过滤用审计模块对getOriginal的重用——browise-audit的AuditEntityLifecycleListener在实体保存前抓快照字段级diff就是拿当前值 vs getOriginal(key)逐字段比——psnName从张三改成李四这种变更记录的两侧数据源就是这两个方法。_o不只服务回滚还免费服务了审计。五、getMap返回引用不是副本publicMapString,ObjectgetMap(){returndata;// 直接返回内部Map}注释明确警告修改返回值会直接影响行数据。为什么不defensive copy性能——每行每字段都copy一份几千行编辑时开销可观。约定替代防御——框架内部使用者RowSet/BaseEntity/审计遵守只读getMap的约定外部要写值必须走setItemValue否则脏标记失效。这个设计的赌注是误用导致的bug改了Map行状态没变比copy的性能损失更容易被发现——界面上改了但提交时没带上这个字段一眼就能看出来。反过来的性能劣化是隐形的。六、toRaw与序列化的边界Row还有toRaw()导出纯数据不含_t/_o和前后端传输的序列化。传输格式{_t:3,name:李四,_o:{name:张三},age:30}_t和_o以字段形式混在JSON里——不是包裹结构。前端useRowSet解析时按约定名提取剩余字段全是业务数据。这个扁平结构的代价业务字段不能叫_t或_o下划线开头的名字被协议保留——14年没出过冲突因为业务字段规范要求驼峰命名。✅ 亮点177行Row拆成三个字段11行状态机重点讲original首次记录不覆盖锚定最初值的语义、INSERT不记旧值的原因、getMap返回引用的性能/可发现性取舍、_t/_o扁平混在JSON的命名保留约定。适合实现脏标记数据结构的参考。扩展方向第18篇RowSet三缓冲、第19篇accept/reject、第44篇前端useRowSet镜像。
返回列表