Class JoinRowReducer<R>

java.lang.Object
org.jdbi.v3.core.result.JoinRowReducer<R>
Type Parameters:
R - the root type, and the result element type
All Implemented Interfaces:
RowReducer<org.jdbi.v3.core.result.JoinRowReducer.Container<R>, R>

@Beta public class JoinRowReducer<R> extends Object implements RowReducer<org.jdbi.v3.core.result.JoinRowReducer.Container<R>, R>
A RowReducer that reduces the rows of a join query into a stream of root objects and links the joined objects to their root. One type is one table: every object is identified by its type and its key column, so a table row is one instance no matter through how many relations the query reaches it.

Columns are named relative to the relation, in the style of @Nested mappers. The root type reads its columns without a prefix. A joined type reads them under the relation prefix and an underscore, and nested relations compound: id, author_id, author_award_id. The key column is id unless of(Class, String) names another. A relation is present on a path when the result set has its key column there, so select the root's own foreign key columns under another label than a relation key, or not at all: with SELECT b.* and a relation category, the book's category_id column alone would produce a Category that has only its key. Keys are compared by value: numeric keys are equal when their values are, whatever their JDBC types, so an INT key matches a BIGINT or a NUMERIC key of the same value, and byte[] keys are compared by content.

List<Book> books = handle.createQuery(
        "SELECT b.id, b.title, a.id author_id, a.name author_name, e.id editor_id, e.name editor_name "
            + "FROM books b JOIN persons a ON a.id = b.author_id JOIN persons e ON e.id = b.editor_id")
    .reduceRows(JoinRowReducer.of(Book.class).mappedBy(BeanMapper::of)
        .one("author", Person.class, Book::setAuthor)
        .one("editor", Person.class, Book::setEditor))
    .toList();

one declares a to-one relation and many a to-many relation, both over inner and outer joins: a NULL key produces no object for that row. A joined type can have relations of its own, declared on a nested reducer that is passed in place of the class. A class that is declared anywhere in the reducer, the root type included, refers to that declaration, so the author and the editor above are one Person instance when they are the same person, and a self join is of(Category.class).one("parent", Category.class, Category::setParent) with the parent being the same instance as the root category with that key. The recursion of a self join ends where the selected columns end: no parent_parent_id column, no grandparent.

Mappers come from the registry by type and prefix, see RowView.getRow(Class, String): the root takes the mapper registered for its type without a prefix, custom or reflective, and each relation path that the query selects takes a mapper registered with its prefix. Or pass a mapper factory such as BeanMapper::of to mappedBy(BiFunction) and register nothing. A factory on the root applies to every type that has none of its own, and a factory is also the way to use a custom row mapper for a joined type.

Linkers run once per distinct pair after the last row, and the relations of a joined type are linked before the joined type is linked to its parent, so a linker receives a complete object unless the graph is recursive. If the query selects a type on several paths, the object is mapped from the shallowest path, and among paths of one depth from the one laid out first, breadth first in declaration order, so the result does not depend on row order. Root objects are returned in the order of their first row, the objects of a many relation are linked to a parent in the order of their first row with it, and only keys that appear in the root key column become roots. A reduction fails when the result set has no root key column, when a declared relation has its key column on no path, when a root mapper returns null, or when a one relation sees a second, different key for the same parent. The column checks run on the first row, so a result set with no rows passes them.

Instances are immutable. Each call to one, many or mappedBy returns a new reducer, and all mutable state lives in the container of one reduction, so a reducer can be shared between statements and threads. The protected copy constructor gives a subclass the public no-arg constructor that @UseRowReducer requires.

  • Method Details

    • of

      public static <R> JoinRowReducer<R> of(Class<R> type)
      Creates a reducer for a type whose key column is id.
      Type Parameters:
      R - the type
      Parameters:
      type - the type, one per table
      Returns:
      a reducer that produces one object per distinct key
    • of

      public static <R> JoinRowReducer<R> of(Class<R> type, String keyColumn)
      Creates a reducer for a type with the given key column.
      Type Parameters:
      R - the type
      Parameters:
      type - the type, one per table
      keyColumn - the column that identifies a row of the table, relative to the relation prefix
      Returns:
      a reducer that produces one object per distinct key
    • mappedBy

      public JoinRowReducer<R> mappedBy(BiFunction<Class<?>, String, ? extends RowMapper<?>> mapperFactory)
      Uses the given factory to create the mapper for a type and column prefix, for example BeanMapper::of, instead of the registered mapper. On the root reducer, the factory also applies to every type in the reducer that has no factory of its own.
      JoinRowReducer.of(Book.class)
          .mappedBy((type, prefix) -> type == Book.class ? bookMapper : BeanMapper.of(type, prefix))
      
      Parameters:
      mapperFactory - creates the mapper for a type and a prefix, the empty prefix for the root
      Returns:
      a new reducer that maps with the factory
    • one

      public <J> JoinRowReducer<R> one(String prefix, Class<J> joinedType, BiConsumer<? super R, ? super J> linker)
      Adds a to-one relation to a type that is declared elsewhere in the reducer, or that has no relations. A type that is declared nowhere gets the key column id; pass of(type, keyColumn) for another.
      Type Parameters:
      J - the joined type
      Parameters:
      prefix - the column prefix of the relation, for example author for author_id
      joinedType - the joined type
      linker - sets the joined object on the parent, for example Book::setAuthor
      Returns:
      a new reducer that also links the joined object
    • one

      public <J> JoinRowReducer<R> one(String prefix, JoinRowReducer<J> joined, BiConsumer<? super R, ? super J> linker)
      Adds a to-one relation to a type with relations of its own.
      Type Parameters:
      J - the joined type
      Parameters:
      prefix - the column prefix of the relation
      joined - the reducer for the joined type, which declares its relations
      linker - sets the joined object on the parent
      Returns:
      a new reducer that also links the joined object
    • many

      public <J> JoinRowReducer<R> many(String prefix, Class<J> joinedType, BiConsumer<? super R, ? super J> linker)
      Adds a to-many relation to a type that is declared elsewhere in the reducer, or that has no relations. A type that is declared nowhere gets the key column id; pass of(type, keyColumn) for another.
      Type Parameters:
      J - the joined type
      Parameters:
      prefix - the column prefix of the relation, for example phone for phone_id
      joinedType - the joined type
      linker - adds the joined object to the parent, for example Contact::addPhone
      Returns:
      a new reducer that also links the joined objects
    • many

      public <J> JoinRowReducer<R> many(String prefix, JoinRowReducer<J> joined, BiConsumer<? super R, ? super J> linker)
      Adds a to-many relation to a type with relations of its own.
      Type Parameters:
      J - the joined type
      Parameters:
      prefix - the column prefix of the relation
      joined - the reducer for the joined type, which declares its relations
      linker - adds the joined object to the parent
      Returns:
      a new reducer that also links the joined objects
    • container

      public org.jdbi.v3.core.result.JoinRowReducer.Container<R> container()
      Description copied from interface: RowReducer
      Returns a new, empty result container.
      Specified by:
      container in interface RowReducer<org.jdbi.v3.core.result.JoinRowReducer.Container<R>, R>
      Returns:
      a new result container.
    • accumulate

      public void accumulate(org.jdbi.v3.core.result.JoinRowReducer.Container<R> container, RowView rowView)
      Description copied from interface: RowReducer
      Accumulate data from the current row into the result container. Do not attempt to accumulate the RowView itself into the result container--it is only valid within the accumulate() method invocation. Instead, extract mapped types from the RowView by calling RowView.getRow() or RowView.getColumn() and store those values in the container.
      Specified by:
      accumulate in interface RowReducer<org.jdbi.v3.core.result.JoinRowReducer.Container<R>, R>
      Parameters:
      container - the result container
      rowView - row view over the current result set row.
    • stream

      public Stream<R> stream(org.jdbi.v3.core.result.JoinRowReducer.Container<R> container)
      Description copied from interface: RowReducer
      Returns a stream of result elements from the result container.
      Specified by:
      stream in interface RowReducer<org.jdbi.v3.core.result.JoinRowReducer.Container<R>, R>
      Parameters:
      container - the result container
      Returns:
      stream of result elements.