2

Can the DefaultRecordMapper handle lists?

Model example (really getter/setter are used):

class Head {
    public Integer id;
    public List<Position> positions;
    ...
}
class Position {
    public Integer id;
    public Integer headId;
    ...
}

I first tried it with a simple select:

dsl.select()
    .from(HEAD)
    .join(POSITION).onKey()
    .fetchInto(Head.class);

Also I have tried using the nested syntax but it didn't work:

dsl.select(..., POSITION.ID.as("positions.id"), ...)
    .from(HEAD)
    .join(POSITION).onKey()
    .fetchInto(Head.class);

The list never gets initialized. I am unsure if I could be doing something wrong. Is this possible with the DefaultRecordMapper?

I am aware that jOOQ provides functionality like "intoGroups". However this would mean that our company cannot use our existing models.

Or are third party libraries like i.e. ModelMapper necessary in this case?

Thanks very much in advance!

Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
db2222
  • 59
  • 4

1 Answers1

1

The recommended approach of nesting collections

The out of the box support for nested collections in jOOQ is via SQL/XML or SQL/JSON, depending on what works best in your SQL dialect.

Starting with jOOQ 3.14, this was offered as an API, which you can use directly, with jOOQ 3.15, new MULTISET and MULTISET_AGG functions were added to keep the serialisation formats transparent to your code, and to re-add type safety, see also this blog post.

Using jOOQ 3.14 SQL/XML and SQL/JSON directly

The idea is that you nest collections directly in SQL, serialise the results as XML or JSON, and use JAXB (for XML), or Gson or Jackson (for JSON) behind the scenes to map the document into a hierarchy of Java classes. All of this is quite straightforward and automatic, as soon as you have either JAXB, Gson, or Jackson on your classpath.

An example for your data structures:

dsl.select(
      HEAD.ID,
      field(
        select(jsonArrayAgg(jsonObject(
          key("id").value(POSITION.ID),
          key("x").value(POSITION.X), ...
        )))
        .from(POSITIONS)
        .where(POSITIONS.HEAD_ID.eq(HEAD.ID))
      ).as("positions")
    )
    .from(HEAD)
    .fetchInto(Head.class);

More examples are documented here:

Note that JSON_ARRAYAGG() aggregates empty sets into NULL, not into an empty []. If that's a problem, use COALESCE()

Using jOOQ 3.15 MULTISET

Starting from jOOQ 3.15, the recommendation is to use MULTISET. Your query would now look like this (all type safe!):

dsl.select(
      HEAD.ID,
      multiset(
        select(POSITION.ID, POSITION.X, ...)
        .from(POSITIONS)
        .where(POSITIONS.HEAD_ID.eq(HEAD.ID))
      ).convertFrom(r -> r.map(Records.mapping(Position::new)))
    )
    .from(HEAD)
    .fetch(Records.mapping(Head::new));

The above is assuming you have appropriate constructors on your types in order to profit from the new ad-hoc converter features.

Other options

There are also a few third parties that help simplify this task of mapping to nested collections, including:

Or, to some extent, you can use jOOQ's various built-in approaches including the many overloads of Result.intoGroups() or JDK Collector based ResultQuery.collect(), which is more composable.

Most of these approaches work to a certain extent only, because they're all based on de-duplicating flat, denormalised result sets that arise from joining tables. Nesting collections directly in SQL is much more powerful for arbitrary target models, so if you get that to work, it'll work much better.

Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
  • Thanks! I have checked. JSON_OBJECT is supported by Oracle since 12.2.0.1.0. FOR JSON is supported by SQL Server since version 2016. For H2 I simply cannot find the info since when JSON_OBJECT was added. Do you know by any chance? These are the dbs our company supports/uses. Unfortunately our customers can use a bit older versions too because we base it on the extended life support of the db manufacturer. So I need to clarify if it's still an option. – db2222 Feb 20 '21 at 07:47
  • The XML functions are only supported with Oracle (related to the 3 dbs mentioned above) according to the jOOQ manual. Is this correct? SQL server itself seems to support FOR XML in all versions (see https://learn.microsoft.com/en-us/sql/relational-databases/xml/for-xml-sql-server?view=sql-server-ver15). – db2222 Feb 20 '21 at 07:51
  • H2 is very much in motion, including subtle backwards incompatibilities in between patch releases. I've stopped keeping track of H2 versions, worrying only about the latest one. I'm curious about the rationale of supporting old H2 versions. With both SQL Server and Oracle, the XML APIs could help here. With the current jOOQ versions, you'd have to generate much for the Oracle/SQL Server specific XML yourself. Future versions will provide a more unified view of this: https://github.com/jOOQ/jOOQ/issues/11487 or even better, nested collections in general: https://github.com/jOOQ/jOOQ/issues/3884 – Lukas Eder Feb 22 '21 at 08:07
  • @db2222: However, this is just the recommended way to nest complex collections, currently, I'll update my answer with other options. We might well re-prioritise a few improvements in the near future if that seems the best way forward for you – Lukas Eder Feb 22 '21 at 08:07
  • Cheers once again :-) Your response times and quality are remarkable! After an analysis JSON/XML do not seem like a good solution for us. Seemingly SimpleFlatMapper is the best option for our case. However some questions have occurred. I will open a new one for that. – db2222 Feb 28 '21 at 12:04
  • @db2222: Yes, I agree. If you want to be highly vendor agnostic, then JSON/XML doesn't work as well as it should, unfortunately. Luckily, at least for your simple example, you don't have to go all in on nesting collections with SQL, yet. – Lukas Eder Mar 01 '21 at 08:05