You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Spring AuditorAware配置Jooq审计用户时插入报错求助

问题描述

尝试通过自定义审计提供者处理用户审计信息,执行插入操作时触发SQL异常,提示SELECT列表中的项数少于INSERT列表,SELECT值的数量必须与INSERT列的数量匹配。

自定义审计提供者代码

public class AuditorAwareJooqProvider extends DefaultAuditProvider {

   private final AuditorAware<String> auditorProvider;

   public AuditorAwareJooqProvider( final AuditorAware<String> auditorProvider ) {
      this.auditorProvider = auditorProvider;
   }

   @Override
   public <T> Field<T> provideUser( final GeneratorContext<?, ?, T> ctx ) {
      final Field<T> f = ctx.field();
      final Class<T> t = f == null ? (Class<T>) String.class : f.getDataType().getType();

      if ( t == String.class ) {
         final String currentAuditor = auditorProvider.getCurrentAuditor().orElseThrow( () -> new IllegalArgumentException( "No Auditor is set" ) );
         return (Field<T>) DSL.val( currentAuditor );
      } else {
         return null;
      }
   }

}

异常信息

org.jooq.exception.DataAccessException: SQL [declare @result table ([id] bigint); insert into [foo] ([name], [created_by], [last_modified_by], [created_at], [last_modified_at]) output inserted.[id] into @result select [name], ?, ? from (select ?, ?, ?) [t] ([name]); select [id] from @result [r];]; The select list for the INSERT statement contains fewer items than the insert list. The number of SELECT values must match the number of INSERT columns.
    at org.jooq_3.18.4.SQLSERVER.debug(Unknown Source)
    at org.jooq.impl.Tools.translate(Tools.java:3470)
    at org.jooq.impl.Tools.translate(Tools.java:3458)
    at org.jooq.impl.DefaultExecuteContext.sqlException(DefaultExecuteContext.java:772)
    at org.jooq.impl.AbstractQuery.execute(AbstractQuery.java:371)
    at org.jooq.impl.TableRecordImpl.storeInsert0(TableRecordImpl.java:197)
    at org.jooq.impl.TableRecordImpl.lambda$storeInsert$0(TableRecordImpl.java:163)
    at org.jooq.impl.RecordDelegate.operate(RecordDelegate.java:144)
    at org.jooq.impl.TableRecordImpl.storeInsert(TableRecordImpl.java:162)
    at 
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: The select list for the INSERT statement contains fewer items than the insert list. The number of SELECT values must match the number of INSERT columns.

jOOQ生成器配置

<forcedType>
    <name>BIGINT</name>
    <auditInsertTimestamp>true</auditInsertTimestamp>
    <includeExpression>created_at</includeExpression>
</forcedType>
<forcedType>
    <name>BIGINT</name>
    <auditUpdateTimestamp>true</auditUpdateTimestamp>
    <auditInsertTimestamp>true</auditInsertTimestamp>
    <includeExpression>last_modified_at</includeExpression>
</forcedType>
<forcedType>
    <auditInsertUser>true</auditInsertUser>
    <includeExpression>created_by</includeExpression>
</forcedType>
<forcedType>
    <auditUpdateUser>true</auditUpdateUser>
    <auditInsertUser>true</auditInsertUser>
    <includeExpression>last_modified_by</includeExpression>
</forcedType>
问题原因

从异常中的SQL语句可见,INSERT需要5列(name, created_by, last_modified_by, created_at, last_modified_at),但SELECT子句仅提供3个值(name, ?, ?),缺少created_at和last_modified_at对应的审计值。

问题根源在于自定义的AuditorAwareJooqProvider仅实现了provideUser方法,未处理时间戳类型的审计字段(created_at、last_modified_at)。jOOQ的DefaultAuditProvider默认会处理时间戳,但自定义审计提供者后,需确保覆盖所有必要的审计方法,或在未处理场景中调用父类实现。

另外,生成器配置中created_at和last_modified_at被标记为BIGINT类型的审计时间戳,需要提供数值型时间值(如时间戳),但当前自定义提供者未处理这类字段,导致jOOQ无法生成对应的SELECT值,最终列数不匹配。

解决方法

有两种可行的解决方案:

方案一:调用父类方法处理非用户类型审计字段

修改AuditorAwareJooqProvider,在非String类型分支中调用父类provideUser实现,同时确保时间戳字段由父类处理:

@Override
public <T> Field<T> provideUser(final GeneratorContext<?, ?, T> ctx) {
    final Field<T> f = ctx.field();
    final Class<T> t = f == null ? (Class<T>) String.class : f.getDataType().getType();

    if (t == String.class) {
        final String currentAuditor = auditorProvider.getCurrentAuditor().orElseThrow(() -> new IllegalArgumentException("No Auditor is set"));
        return (Field<T>) DSL.val(currentAuditor);
    } else {
        // 调用父类方法处理非String类型的用户字段
        return super.provideUser(ctx);
    }
}

// 覆盖provideTimestamp方法,确保时间戳字段处理正常
@Override
public <T> Field<T> provideTimestamp(final GeneratorContext<?, ?, T> ctx) {
    // 如需自定义时间戳逻辑可在此实现,否则直接调用父类方法
    return super.provideTimestamp(ctx);
}

该方案既保留自定义用户审计逻辑,又让父类处理时间戳类型审计字段,保证INSERT与SELECT列数一致。

方案二:完全自定义所有审计字段处理逻辑

若需完全自定义所有审计逻辑,需在provideTimestamp方法中实现BIGINT类型时间戳的生成,比如返回当前时间毫秒数:

@Override
public <T> Field<T> provideTimestamp(final GeneratorContext<?, ?, T> ctx) {
    final Field<T> f = ctx.field();
    final Class<T> t = f.getDataType().getType();

    if (t == Long.class) {
        // 返回当前时间的毫秒时间戳,适配BIGINT类型
        return (Field<T>) DSL.val(System.currentTimeMillis());
    }
    return super.provideTimestamp(ctx);
}

同时保留provideUser方法现有逻辑,确保所有审计字段都能生成对应的SELECT值,解决列数不匹配问题。

内容的提问来源于stack exchange,提问作者MelleD

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 07:12:55