如何在jOOQ的VisitListener中为多行操作正确设置StoreQuery审计字段?
问题1:不执行auditFields.clear()时,无审计字段的表也会被添加审计字段的原因
你的AuditVisitListener实例会被jOOQ复用(即使配置类中每次通过Provider新建实例,jOOQ在查询上下文或缓存机制中仍可能复用该实例)。auditFields是类的实例变量,若不在visitEnd中清空,上一次查询收集到的审计字段会残留。当下一次处理无审计字段的表时,auditFields中仍保留之前的字段记录,导致addAuditFieldsForInsert/Update方法执行storeQuery.addValue(),强行将审计字段添加到SQL中。
问题2:链式.values()多行插入仅最后一行设置审计字段的解决方案
问题原因
链式调用.values()构建的是InsertValuesStepN类型的插入语句,所有行数据维护在同一个InsertQuery中。StoreQuery.addValue()方法仅对当前最后一行设置值,而非所有行。这就导致只有最后一行被填充审计字段值,前面的行仅在列列表中添加了审计字段,但值为null。
而batchInsert是创建多个独立的InsertQuery,每个查询都会触发一次visitStart,因此每一行都会被正确设置审计字段。
修复方案
方案1:遍历所有行设置审计字段
修改addAuditFieldsForInsert方法,针对多行插入场景遍历所有行,逐个设置审计字段值:
private void addAuditFieldsForInsert(StoreQuery<?> storeQuery) { if (!(storeQuery instanceof InsertQuery<?> insertQuery)) { return; } Timestamp now = now(); String userId = userId(); List<Row> rows = insertQuery.getValues(); // 遍历所有行添加审计字段值 for (Row row : rows) { if (auditFields.contains(createdAt)) { row.add(createdAt, now); } if (auditFields.contains(createdBy)) { row.add(createdBy, userId); } if (auditFields.contains(updatedAt)) { row.add(updatedAt, now); } if (auditFields.contains(updatedBy)) { row.add(updatedBy, userId); } } // 确保审计字段被添加到插入列列表 if (auditFields.contains(createdAt) && !insertQuery.getFields().contains(createdAt)) { insertQuery.addField(createdAt); } if (auditFields.contains(createdBy) && !insertQuery.getFields().contains(createdBy)) { insertQuery.addField(createdBy); } if (auditFields.contains(updatedAt) && !insertQuery.getFields().contains(updatedAt)) { insertQuery.addField(updatedAt); } if (auditFields.contains(updatedBy) && !insertQuery.getFields().contains(updatedBy)) { insertQuery.addField(updatedBy); } }
方案2:使用set方法统一设置所有行
改用StoreQuery.set()方法替代addValue(),该方法会为所有行设置字段值(需确保字段已添加到插入列列表):
private void addAuditFieldsForInsert(StoreQuery<?> storeQuery) { if (auditFields.contains(createdAt)) { storeQuery.set(createdAt, now()); } if (auditFields.contains(createdBy)) { storeQuery.set(createdBy, userId()); } addAuditFieldsForUpdate(storeQuery); } private void addAuditFieldsForUpdate(StoreQuery<?> storeQuery) { if (auditFields.contains(updatedAt)) { storeQuery.set(updatedAt, now()); } if (auditFields.contains(updatedBy)) { storeQuery.set(updatedBy, userId()); } }
额外优化建议
collectAuditFields方法中,使用table.field(createdAt)判断字段存在性的方式不够可靠(未绑定表的字段可能无法匹配),建议改用表的元数据或代码生成的字段常量判断:
// 示例:通过字段名称判断 if (table.getField("created_at") != null) { auditFields.add(createdAt); } // 若使用代码生成的表,可直接用生成的字段常量 // if (BRAND.CREATED_AT != null) { ... }
内容的提问来源于stack exchange,提问作者Elyorbek Ibrokhimov

