There's a conceptual mismatch between what the object model would like to express, and what SQL is able to produce. In the object model, people like to model relationships as nested collections, such as:
Author {
List<Book> books;
}
Whereas in an RDBMS, people model relationships the other way round, having foreign keys on child entities:
CREATE TABLE book (
author_id bigint REFERENCES author(id)
)
The most straightforward way to produce a nested collection (as in the object model) from SQL would be to use the MULTISET
operator, as specified in the SQL standard:
SELECT
author.*,
MULTISET(
SELECT * FROM book WHERE book.author_id = author.id
) AS books
FROM author;
Unfortunately, this operator is only supported in few databases, including Cubrid, Informix, Oracle, and it can be emulated in PostgreSQL. Note that future versions of jOOQ will also support, and possibly emulate it.
Lacking support for this feature, people often resort to using tricks, such as:
- Lazy loading
- Running a new query per entity
- Running a single query with
LEFT JOIN
, the way you did
All of these approaches are tricks employed by ORMs like Hibernate, each with their individual drawback, which is documented in this blog post. ORMs don't always make the "right" choice of trick (or rather: users don't configure ORMs correctly), which is why they run a lot of queries causing performance issues.
One of the best approaches among the above is probably the "new query per entity" approach, where you first fetch all the authors, and then all the books for each author in 2 queries (not in N+1 queries!):
SELECT * FROM author WHERE [ some predicate ]
SELECT * FROM book WHERE author_id IN (SELECT id FROM author WHERE [ some predicate ])
You can then materialise each result with jOOQ, and use hashmaps to match authors and books in Java memory.