使用 jOOQ 转换 PostgreSQL UUID 数组的类型

Convert types of PostgreSQL UUID arrays using jOOQ

我正在尝试将 PostgreSQL (9.4) UUID 数组绑定到每个 UUID 周围的包装器 class 的数组。请注意,这些包装器集成在整个应用程序中,因此无法删除它们。我正在使用 jOOQ 和它的 Maven 插件 (3.5.1) 来生成 PoJo 的 RecordTable classes.

我在绑定时遇到问题的数据库模式的相关部分类似于:

create table foo (
    id uuid primary key,
    ids uuid[] not null
);

然后我尝试使用 forcedType 元素转换 class,但是它生成的 classes 无法编译。放弃了,我只是打算让它们作为 UUID 生成,直到我遇到一些问题,其中数组值未转换为 uuid,并且 PostgreSQL 认为该数组是我查询中的文本数组 [1]。

为了解决这个问题,我尝试添加一个 Binding [2] 和一个 Converter [3],其中转换器用我们的包装器包装 UUID 和绑定将强制转换表达式添加到生成的 SQL。如果我编写流畅的查询 [4],这会很好地工作,但是在插入 Record [5] 时无法正常工作。当我逐个构建数组查询时,插入语句以 'array.length - 1' 参数化部分结束。我怀疑我需要重写 Binding class 的 #get#set 方法,但是我发现文档在这方面有点简单。

所以我的问题是,在使用或不使用 Binding class 的情况下,在 jOOQ 中绑定 UUID 数组的正确方法是什么?另外,在这个过程中是否可以将其转换为T数组?

  1. 查询(名称已更改)
public BazId getBazIdBySearchingFooIdsInReverse(
        @NotNull final OtherId otherId,
        @NotNull final SomethingId somethingId,
        @NotNull final String barTypeName,
        @NotNull final SomethingElseId somethingElseId) {
    final Bar bar = BAR.as("bar");
    final Foo foo = FOO.as("foo");
    return db.select(BAZ.ID)
             .from(BAZ)
             .join(bar)
             .on(BAZ.SOMETHING_ID.eq(bar.SOMETHING_ID))
             .join(foo)
             .on(bar.FOO_ID.eq(foo.ID))
             .join("lateral unnest(foo.ids) with ordinality as x (id,ord)")
             .on("x.id=foo.id")
             .join(BAR_TYPE)
             .on(bar.BAR_TYPE_ID.eq(BAR_TYPE.ID)
                                .and(BAR_TYPE.NAME.equalIgnoreCase(barTypeName)))
             .where(BAZ.SOMETHING_ID.eq(somethingId))
             .and(BAZ.SOMETHING_ELSE_ID.eq(somethingElseId))
             .and(bar.OTHER_ID.eq(otherId))
             .orderBy(DSL.field("x.ord").desc())
             .limit(1)
             .fetchOneInto(BazId.class); //Wraps a UUID
}
  1. 绑定
public class FooIdsBinding extends DefaultBinding<Object[], FooId[]> {
    private static final long serialVersionUID = 0L;

    private static final UUIDConverter converter = new UUIDConverter();

    public FooIdsBinding() {
        super(new FooIdsConverter());
    }

    @Override
    public void sql(final BindingSQLContext<FooId[]> ctx) {
        final RenderContext render = ctx.render();
        render.sql("array[");
        final UUID[] uuids = ctx.convert(converter).value();
        for (int i = 0, last = uuids.length - 1; i <= last; ++i) {
            render.visit(DSL.val(uuids[i])).sql("::uuid");
            if (i != last) {
                render.sql(',');
            }
        }
        render.sql("]::uuid[]");
    }

    @Override
    public void register(final BindingRegisterContext<FooId[]> ctx) throws SQLException {
        ctx.statement().registerOutParameter(ctx.index(), Types.ARRAY);
    }

    static class BaseUUIDConverter {

        public FooId[] from(final Object[] from) {
            return from == null ? null : Arrays.stream(from)
                                               .map(that -> new FooId((UUID)that))
                                               .collect(Collectors.toList())
                                               .toArray(new FooId[from.length]);
        }

        public UUID[] to(final FooId[] from) {
            return from == null ? null : Arrays.stream(from)
                                               .map(FooId::getUuid)
                                               .collect(Collectors.toList())
                                               .toArray(new UUID[from.length]);
        }

        public Class<FooId[]> toType() {
            return FooId[].class;
        }
    }

    private static class UUIDConverter extends BaseUUIDConverter implements Converter<UUID[], FooId[]> {

        @Override
        public FooId[] from(final UUID[] that) {
            return super.from(that);
        }

        @Override
        public Class<UUID[]> fromType() {
            return UUID[].class;
        }
    }
}
  1. 转换器。必须是 Object[] 才能使生成的 Table 编译
public class FooIdConverter extends FooIdsBinding.BaseUUIDConverter implements Converter<Object[],FooId[]> {
    private static final long serialVersionUID = 0L;

    @Override
    public Class<Object[]> fromType() {
        return (Class)UUID[].class;
    }
}
  1. 有效的查询
    db.insertInto(FOO)
      .set(FOO.ID, new FooId())
      .set(FOO.IDS, new FooId[]{new FooId(),new FooId()})
      .execute();
  1. 不存在的查询
    foo = new FooRecord();
    foo.setId(new FooId());
    foo.setIds(new FooId[]{new FooId(),new FooId()});
    db.executeInsert(foo);

更新

我最终得到了这个绑定和转换器,它工作正常。我原以为我需要将数组的每个元素转换为一个 uuid,我的实现导致 jOOQ 的 sql 生成出现问题,但我认为我只在 jOOQ 覆盖之前看到与此相关的错误 Binding#register 为数组。

  1. 转换器

    public class FooIdConverter implements Converter<Object[],FooId[]> {
    
        private static final long serialVersionUID = 1L;
    
        @Override
        public FooId[] from(final Object[] from) {
            return from == null ? null : Arrays.stream(from)
                                               .map(that -> new FooId((UUID)that))
                                               .collect(Collectors.toList())
                                               .toArray(new FooId[from.length]);
        }
    
        @Override
        public UUID[] to(final FooId[] from) {
            return from == null ? null : Arrays.stream(from)
                                               .map(FooId::getUuid)
                                               .collect(Collectors.toList())
                                               .toArray(new UUID[from.length]);
        }
    
        @Override
        @SuppressWarnings("unchecked")
        public Class<Object[]> fromType() {
            return (Class)UUID[].class;
        }
    
        @Override
        public Class<FooId[]> toType() {
            return FooId[].class;
        }
    }
    
  2. 绑定

    public class FooIdBinding extends DefaultBinding<Object[], FooId[]> {
    
        private static final long serialVersionUID = 1L;
    
        public FooIdBinding() {
            super(new FooIdConverter());
        }
    
        @Override
        public void sql(final BindingSQLContext<FooId[]> ctx) {
            super.sql(ctx);
            ctx.render().sql("::uuid[]");
        }
    
        @Override
        public void register(final BindingRegisterContext<FooId[]> ctx) throws SQLException {
            ctx.statement().registerOutParameter(ctx.index(), Types.ARRAY);
        }
    
        @Override
        public void get(final BindingGetResultSetContext<FooId[]> ctx) throws SQLException {
            ctx.value(_convert(ctx.resultSet().getArray(ctx.index())));
        }
    
        @Override
        public void get(final BindingGetStatementContext<FooId[]> ctx) throws SQLException {
            ctx.value(_convert(ctx.statement().getArray(ctx.index())));
        }
    
        @Override
        public void get(final BindingGetSQLInputContext<FooId[]> ctx) throws SQLException {
            ctx.value(_convert(ctx.input().readArray()));
        }
    
        @Override
        public void set(final BindingSetStatementContext<FooId[]> ctx) throws SQLException {
            final PreparedStatement ps = ctx.statement();
            ps.setArray(ctx.index(), ps.getConnection().createArrayOf("uuid", ctx.value()));
        }
    
        @Override
        public void set(final BindingSetSQLOutputContext<FooId[]> ctx) throws SQLException {
            throw new UnsupportedOperationException();
        }
    
        protected FooId[] _convert(final Array array) throws SQLException {
            if (array == null) {
                return null;
            } else {
                return converter().from(((UUID[]) array.getArray()));
            }
        }
    }
    

jOOQ 代码生成器中似乎存在阻止 UUID[] 类型被覆盖的错误:#4388