Search code examples
jooqjooq-codegen

Jooq Records.Mapping does not find Constructor in case of Multiple Constructors


When using the "Pojo" classes created by the Jooq-Codegenerator, they (may) contain multiple constructors (default-constructor, interface-constructor, fields-constructor). For example:

public class ProductLang implements IProductLang {
    ...
    public ProductLang() {}

    public ProductLang(IProductLang value) {
        this.productId = value.getProductId();
        this.langId = value.getLangId();
        this.name = value.getName();
        this.description = value.getDescription();
    }

    public ProductLang(
        Long productId,
        Integer langId,
        String name,
        String description
    ) {
        this.productId = productId;
        this.langId = langId;
        this.name = name;
        this.description = description;
    }
    ...

When this Pojo is now used with Records.mapping from RecordMapping-Api (see: https://www.jooq.org/doc/latest/manual/sql-execution/fetching/recordmapper/) within ad-hoc-converters (see: https://www.jooq.org/doc/latest/manual/sql-execution/fetching/ad-hoc-converter/), the Records.mapping can not find the correct constructor, and shows an error.

Following example:

RecordMapper<Record4<Long, Integer, String, String>, ProductLang> r = Records.mapping(ProductLang::new);

Shows error:

  • Cannot resolve constructor 'ProductLang'

When the other two constructors (default-constructor, interface-constructor) are removed from the Pojo the error goes away, and the correct constructor is resolved.

The same problem is also reproducible when using the Records.mapping within the convertFrom function within a jooq-dsl-query.

...
multiset(
    selectDistinct(
        PRODUCT_LANG.PRODUCTID,
        PRODUCT_LANG.LANGID,
        PRODUCT_LANG.NAME,
        PRODUCT_LANG.DESCRIPTION

    )
        .from(PRODUCT_LANG)
        .where(PRODUCT_LANG.PRODUCTID.eq(PRODUCT.PRODUCTID))
).convertFrom(r -> r.map(Records.mapping(ProductLangDTO::new)))
...
  • Can / Is Records.mapping intended to be used with multiple constructors?
    • If yes, what am i missing to make it work.
    • If no, what would be the recommended way for Record-Mapping such multi-constructor Classes.

Solution

  • That's just how Java works with overloading (both Records::mapping and ProductLangDTO::new being overloaded), type inference, and method references. The whole expression becomes ambiguous to the compiler. It would have been possible to infer much more going back and forth through expression trees, but more sophisticated inference was abandoned in JEP 101 and related work (perhaps it's also simply not possible in this particular case - I don't really recall it).

    You can either use a lambda to disambiguate:

    r.map(mapping((a, b, c, d) -> new ProductLangDTO(a, b, c, d)))
    

    Or if that's too lame, avoid the overloaded constructor, e.g. like this:

    public class ProductLang implements IProductLang {
        public static ProductLang copy(IProductLang value) {
            return new ProductLang(
                value.getProductId(),
                value.getLangId(),
                value.getName(),
                value.getDescription()
            );
        }
        ...
    

    There are many other ways to solve this, too, including adding mapping utilities to your DTO (instead of calling Records.mapping()), etc.