欢迎光临
我们一直在努力

18-RowSet三缓冲

18-RowSet三缓冲:primary、filter、delete

Row管一行,RowSet管一组行的生命周期。三个ArrayList——primary存有效行、filter存筛选项、delete存待同步的删除行。remove对INSERT行的特殊处理、collect的脏数据收集、reset的整体重建,这篇把222行拆完。

文章目录

  • 18-RowSet三缓冲:primary、filter、delete
    • 一、三个缓冲的职责
    • 二、add:两行代码创建INSERT行
    • 三、remove:INSERT行的分岔
    • 四、collect:脏数据收集
    • 五、accept与reject:提交后的双向清算
    • 六、reset:查询结果的整体灌入
    • 七、getrow这个命名

源码:browise-data/src/main/java/com/browise/data/RowSet.java(222行)


一、三个缓冲的职责

public class RowSet {
private final List<Row> primary = new ArrayList<>(); // 当前有效行
private final List<Row> filter = new ArrayList<>(); // 筛选结果
private final List<Row> delete = new ArrayList<>(); // 待同步的已删行
}

缓冲行状态谁写谁读
primary NONE/INSERT/UPDATE add/remove/查询灌入 渲染/编辑
filter 任意 本地筛选(客户端过滤) 渲染筛选视图
delete DELETE remove移入 collect提交

filter是三缓冲里最"轻"的——它只是primary某个时刻的视图快照,不参与提交协议。真正的提交协议由 primary+delete 两个缓冲构成。


二、add:两行代码创建INSERT行

public Row add(Map<String, Object> data) {
Row row = new Row(data);
row.setStatus(RowStatus.INSERT); // 新行天生INSERT
primary.add(row);
return row;
}

注意 new Row(data) 构造时状态是NONE(第17篇),add里立刻改成INSERT——“这一行是用户刚创建的,库里没有”。返回row引用让调用方继续填字段:

Row newRow = rowSet.add();
newRow.setItemValue("name", "张三"); // INSERT状态下不记original(第17篇)


三、remove:INSERT行的分岔

public void remove(Row row) {
int idx = primary.indexOf(row);
if (idx >= 0) {
primary.remove(idx);
if (row.getStatus() != RowStatus.INSERT) {
row.setStatus(RowStatus.DELETE);
delete.add(row); // 非INSERT行进delete缓冲
}
// INSERT行:从primary删掉就完了——库里没它,无需同步
}
}

这是三缓冲设计最关键的一行判断:

被删行的状态行为原因
INSERT 从primary移除,不进delete 新增未入库,删了=从未存在,提交时无需任何SQL
NONE/UPDATE 移除+标DELETE+进delete缓冲 库里有这行,提交时要发DELETE

如果不分岔、INSERT行也进delete——提交时对库里不存在的行发DELETE,Oracle报"0行受影响"(不报错但日志脏);更糟的是和数据库触发器/审计联动时会误判。

removeAt(int)是同一逻辑的索引版——两份代码没有互相复用(一个indexOf先),因为实现都只有8行,抽公共方法的成本大于重复。


四、collect:脏数据收集

public List<Row> collect() {
List<Row> dirty = new ArrayList<>();
for (Row row : primary) {
if (row.isDirty()) dirty.add(row); // INSERT/UPDATE行
}
dirty.addAll(delete); // DELETE行
return dirty;
}

三行核心逻辑——primary里的脏行 + delete缓冲的全部行。这个列表就是提交报文里rows数组的来源(第23篇commonSave按_t分发)。

注意collect不改变任何状态——纯只读操作。收集完行还是脏的,直到accept或reject才清。

前端useRowSet的collect(‘auto’|‘all’|‘dirty’)多一个模式参数——后端只有dirty语义(提交协议只需要脏数据)。'all’是前端导出场景(把整个primary交出去)。


五、accept与reject:提交后的双向清算

(第19篇专门讲,这里先给对照)

public void accept() {
// 存活行全部归零
for (Row row : primary) {
row.setStatus(RowStatus.NONE);
row.getOriginals().clear();
}
delete.clear();
filter.clear();
}

public void reject() {
// ①倒序移除INSERT行
for (int i = primary.size() 1; i >= 0; i) {
if (primary.get(i).getStatus() == RowStatus.INSERT) primary.remove(i);
}
// ②UPDATE行从originals整体恢复
for (Row row : primary) {
if (row.getStatus() == RowStatus.UPDATE) {
row.getMap().clear();
row.getMap().putAll(row.getOriginals());
row.getOriginals().clear();
row.setStatus(RowStatus.NONE);
}
}
delete.clear();
filter.clear();
}

一个值得注意的细节:reject恢复UPDATE行用的是getMap().clear()+putAll(originals)整体替换,而不是逐字段original回填。因为originals只记录了改过的字段——整体替换相当于"没改过的字段保留data原值(本来就等于original),改过的字段回到original"。两种写法结果等价,clear+putAll避免了逐字段比较。

还有个隐蔽点——reject后delete里的行丢了!delete.clear()直接清空,被删的行没有回到primary。这是browise与PB DataWindow的行为差异(PB的reject会把delete缓冲的行恢复回primary)——第19篇讲修复历史时展开。


六、reset:查询结果的整体灌入

public void reset(List<Map<String, Object>> rows) {
primary.clear();
filter.clear();
delete.clear();
if (rows != null) {
for (Map<String, Object> m : rows) {
primary.add(new Row(m)); // 新Row状态=NONE
}
}
}

每次查询后全量重建——不是增量merge。旧primary里的脏行、delete缓冲全部丢弃。

为什么不做增量(比如按主键merge保留本地未提交的编辑)?——场景不需要。查询通常是"重新取数",用户点了刷新就是想看最新数据;本地编辑要么先提交要么先放弃。增量merge带来的主键冲突、状态合并复杂度远超收益。KISS在这个数据结构上贯彻得很彻底。


七、getrow这个命名

public Row getrow(int index) { return primary.get(index); }

全小写getrow(不是getRow)——从wiserise的Java代码继承的原名。改名是一行事,但前端useRowSet.ts、所有调用点、以及两代项目共用的心智模型都要跟着动。协议稳定的成本意识贯穿到方法命名——不是不能改,是不值得改。


✅ 亮点:222行RowSet拆成三缓冲职责表、remove对INSERT行不进delete缓冲的关键分岔、collect三行逻辑的提交协议角色、reset全量重建不做增量merge的KISS取舍、getrow命名的协议稳定成本。适合实现行集管理的参考。扩展方向:第19篇accept/reject修复史、第23篇commonSave、第44篇前端useRowSet。

赞(0)
未经允许不得转载:171主机测评 » 18-RowSet三缓冲
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址