java 如何在jOOQ中为VisitListener中的每一行正确地添加值到StoreQuery中?

j9per5c4  于 2023-05-27  发布在  Java
关注(0)|答案(1)|浏览(167)

我正在尝试仅当表具有审核字段时才为每个插入或更新查询设置审核字段。经过大量的尝试和错误,我使用VisitListener得出了这个逻辑:

public class AuditVisitListener implements VisitListener {

    private static final Field<Timestamp> createdAt = field("created_at", Timestamp.class);
    private static final Field<String> createdBy = field("created_by", String.class);
    private static final Field<Timestamp> updatedAt = field("updated_at", Timestamp.class);
    private static final Field<String> updatedBy = field("updated_by", String.class);
    private Set<Field<?>> auditFields = new HashSet<>();

    private UserContextHolder userContextHolder;

    public AuditVisitListener(UserContextHolder userContextHolder) {
        this.userContextHolder = userContextHolder;
    }

    @Override
    public void visitStart(VisitContext context) {
        QueryPart topLevelQuery = context.context().topLevel();
        if(topLevelQuery instanceof SelectQuery<?>) {
            // No need to process further since we don't set audit fields for select queries
            return;
        }
        collectAuditFields(context.queryParts());
        if(topLevelQuery instanceof InsertQuery<?> insertQuery) {
            addAuditFieldsForInsert(insertQuery);
        }
        if(topLevelQuery instanceof UpdateQuery<?> updateQuery) {
            addAuditFieldsForUpdate(updateQuery);
        }
    }

    @Override
    public void visitEnd(VisitContext context) {
        // Without this line, audit fields are added
        // even if there is no audit fields in a table. 
        auditFields.clear();
    }

    private void collectAuditFields(QueryPart[] queryParts) {
        for(QueryPart queryPart : queryParts) {
           if(queryPart instanceof Table<?> table) {
               if(table.field(createdAt) != null){
                   auditFields.add(createdAt);
               }
               if(table.field(createdBy) != null) {
                  auditFields.add(createdBy);
               }
               if(table.field(updatedAt) != null) {
                  auditFields.add(updatedAt);
               }
               if(table.field(updatedBy) != null) {
                   auditFields.add(updatedBy);
               }
           }
        }
    }

    private void addAuditFieldsForInsert(StoreQuery<?> storeQuery) {
        if(auditFields.contains(createdAt)) {
            storeQuery.addValue(createdAt, now());
        }
        if(auditFields.contains(createdBy)) {
            storeQuery.addValue(createdBy, userId());
        }
        addAuditFieldsForUpdate(storeQuery);
    }

    private void addAuditFieldsForUpdate(StoreQuery<?> storeQuery) {
        if(auditFields.contains(updatedAt)) {
            storeQuery.addValue(updatedAt, now());
        }
        if(auditFields.contains(updatedBy)) {
            storeQuery.addValue(updatedBy, userId());
        }
    }

    private String userId() {
        return userContextHolder.getUserId()
                .orElseThrow(() -> new BusinessException(ErrorCode.EMPTY_USER_CONTEXT));
    }

}

第一个问题是没有auditFields.clear(),即使表中没有审计字段,也会添加审计字段。为什么会这样呢?第二个问题是,它只适用于单行插入,但当插入语句有多行时,它只将审计字段设置为最后一行。例如这个insert语句:

ctx.insertInto(BRAND, BRAND.NAME_EN, BRAND.NAME_KR, BRAND.STATUS)
            .values("Heinz", "하인즈", (short)1)
            .values("Hunts", "헌츠", (short)1)
            .execute();

生成此查询:

insert into `pms`.`brand` (`name_en`, `name_kr`, `status`, created_at, created_by, updated_at, updated_by) 
values ('Heinz', '하인즈', 1, null, null, null, null),
       ('Hunts', '헌츠', 1, current_timestamp(), 'test@test.com', current_timestamp(), 'test@test.com')

但是,如果我插入多行,批量插入它再次工作:

BrandRecord heinz = ctx.newRecord(BRAND);
    heinz.setNameEn("Heinz");
    heinz.setNameKr("하인즈");
    heinz.setStatus((short)1);

    BrandRecord hunts = ctx.newRecord(BRAND);
    hunts.setNameEn("Hunts");
    hunts.setNameKr("헌츠");
    hunts.setStatus((short)1);

    ctx.batchInsert(heinz, hunts).execute();

如何为多行插入或更新语句设置审核字段?
作为参考,使用VisitListenerProvider配置AuditVisitListener

@Configuration
public class JooqConfig {

    @Bean
    public DefaultConfigurationCustomizer customizer(UserContextHolder userContextHolder) {
        return configuration -> {
            configuration.setVisitListenerProvider(() -> new AuditVisitListener(userContextHolder));
        };
    }

}

jOOQ:3.18
java :17人
Sping Boot :3.0.5

lvmkulzt

lvmkulzt1#

为什么需要auditFields.clear()

这说明了你的设计中存在一个更大的问题。VisitListener的生命周期可能不是每个查询呈现,而是每个Configuration,这意味着各种查询共享同一个示例。这意味着clear()似乎“修复”了这个问题,但实际上隐藏了一个更大的并发问题。
或者,您应该使用每次都示例化新VisitListener示例的VisitListenerProvider,或者将您的上下文放在VisitContext中,其生命周期是单个渲染遍历的生命周期。

为什么只对最后一行有效

(非常历史性的)InsertQueryUpdateQuery API只允许将数据追加到最后一行。要添加新行,调用InsertQuery.newRecord()。从那时起,InsertQuery中就没有公共API可以将值追加到前面的行。
对于批处理查询则不是这样,它是多个查询,而不是每个查询多行。
较新的model API(从jOOQ 3.18开始仍处于试验阶段)将允许您访问VALUES子句中的所有行。它还允许编写更简单的SQL转换逻辑,不需要VisitListener SPI。

关于备选方案的说明

请注意,jOOQ具有:

这将是 * 容易得多 * 只是工作,或者,作为选择,使用触发器来填充这些列值。

相关问题