使用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

