package io.ebean;

import java.io.Serializable;

Defines the configuration options for a "query fetch" or a "lazy loading fetch". This gives you the ability to use multiple smaller queries to populate an object graph as opposed to a single large query.

The primary goal is to provide efficient ways of loading complex object graphs avoiding SQL Cartesian product and issues around populating object graphs that have multiple *ToMany relationships.

It also provides the ability to control the lazy loading queries (batch size, selected properties and fetches) to avoid N+1 queries etc.

There can also be cases loading across a single OneToMany where 2 SQL queries using Ebean FetchConfig.query() can be more efficient than one SQL query. When the "One" side is wide (lots of columns) and the cardinality difference is high (a lot of "Many" beans per "One" bean) then this can be more efficient loaded as 2 SQL queries.


// Normal fetch join results in a single SQL query
List<Order> list = DB.find(Order.class).fetch("details").findList();
// Find Orders join details using a single SQL query

Example: Using a "query join" instead of a "fetch join" we instead use 2 SQL queries


// This will use 2 SQL queries to build this object graph
List<Order> list =
    DB.find(Order.class)
        .fetch("details", new FetchConfig().query())
        .findList();
// query 1) find order
// query 2) find orderDetails where order.id in (?,?...) // first 100 order id's

Example: Using 2 "query joins"


// This will use 3 SQL queries to build this object graph
List<Order> list =
    DB.find(Order.class)
        .fetch("details", new FetchConfig().query())
        .fetch("customer", new FetchConfig().queryFirst(5))
        .findList();
// query 1) find order
// query 2) find orderDetails where order.id in (?,?...) // first 100 order id's
// query 3) find customer where id in (?,?,?,?,?) // first 5 customers

Example: Using "query joins" and partial objects


// This will use 3 SQL queries to build this object graph
List<Order> list =
    DB.find(Order.class)
        .select("status, shipDate")
        .fetch("details", "quantity, price", new FetchConfig().query())
        .fetch("details.product", "sku, name")
        .fetch("customer", "name", new FetchConfig().queryFirst(5))
        .fetch("customer.contacts")
        .fetch("customer.shippingAddress")
        .findList();
// query 1) find order (status, shipDate)
// query 2) find orderDetail (quantity, price) fetch product (sku, name) where
// order.id in (?,? ...)
// query 3) find customer (name) fetch contacts (*) fetch shippingAddress (*)
// where id in (?,?,?,?,?)
// Note: the fetch of "details.product" is automatically included into the
// fetch of "details"
//
// Note: the fetch of "customer.contacts" and "customer.shippingAddress"
// are automatically included in the fetch of "customer"

You can use query() and lazy together on a single join. The query is executed immediately and the lazy defines the batch size to use for further lazy loading (if lazy loading is invoked).


List<Order> list =
    DB.find(Order.class)
        .fetch("customer", new FetchConfig().query(10).lazy(5))
        .findList();
// query 1) find order
// query 2) find customer where id in (?,?,?,?,?,?,?,?,?,?) // first 10 customers
// .. then if lazy loading of customers is invoked
// .. use a batch size of 5 to load the customers

Example of controlling the lazy loading query:

This gives us the ability to optimise the lazy loading query for a given use case.


List<Order> list = DB.find(Order.class)
  .fetch("customer","name", new FetchConfig().lazy(5))
  .fetch("customer.contacts","contactName, phone, email")
  .fetch("customer.shippingAddress")
  .where().eq("status",Order.Status.NEW)
  .findList();
// query 1) find order where status = Order.Status.NEW
//
// .. if lazy loading of customers is invoked
// .. use a batch size of 5 to load the customers
Author:mario, rbygrave
/** * Defines the configuration options for a "query fetch" or a * "lazy loading fetch". This gives you the ability to use multiple smaller * queries to populate an object graph as opposed to a single large query. * <p> * The primary goal is to provide efficient ways of loading complex object * graphs avoiding SQL Cartesian product and issues around populating object * graphs that have multiple *ToMany relationships. * </p> * <p> * It also provides the ability to control the lazy loading queries (batch size, * selected properties and fetches) to avoid N+1 queries etc. * <p> * There can also be cases loading across a single OneToMany where 2 SQL queries * using Ebean FetchConfig.query() can be more efficient than one SQL query. * When the "One" side is wide (lots of columns) and the cardinality difference * is high (a lot of "Many" beans per "One" bean) then this can be more * efficient loaded as 2 SQL queries. * </p> * <p> * <pre>{@code * // Normal fetch join results in a single SQL query * List<Order> list = DB.find(Order.class).fetch("details").findList(); * * // Find Orders join details using a single SQL query * }</pre> * <p> * Example: Using a "query join" instead of a "fetch join" we instead use 2 SQL queries * </p> * <p> * <pre>{@code * * // This will use 2 SQL queries to build this object graph * List<Order> list = * DB.find(Order.class) * .fetch("details", new FetchConfig().query()) * .findList(); * * // query 1) find order * // query 2) find orderDetails where order.id in (?,?...) // first 100 order id's * * }</pre> * <p> * Example: Using 2 "query joins" * </p> * <p> * <pre>{@code * * // This will use 3 SQL queries to build this object graph * List<Order> list = * DB.find(Order.class) * .fetch("details", new FetchConfig().query()) * .fetch("customer", new FetchConfig().queryFirst(5)) * .findList(); * * // query 1) find order * // query 2) find orderDetails where order.id in (?,?...) // first 100 order id's * // query 3) find customer where id in (?,?,?,?,?) // first 5 customers * * }</pre> * <p> * Example: Using "query joins" and partial objects * </p> * <p> * * <pre>{@code * // This will use 3 SQL queries to build this object graph * List<Order> list = * DB.find(Order.class) * .select("status, shipDate") * .fetch("details", "quantity, price", new FetchConfig().query()) * .fetch("details.product", "sku, name") * .fetch("customer", "name", new FetchConfig().queryFirst(5)) * .fetch("customer.contacts") * .fetch("customer.shippingAddress") * .findList(); * * // query 1) find order (status, shipDate) * // query 2) find orderDetail (quantity, price) fetch product (sku, name) where * // order.id in (?,? ...) * // query 3) find customer (name) fetch contacts (*) fetch shippingAddress (*) * // where id in (?,?,?,?,?) * * // Note: the fetch of "details.product" is automatically included into the * // fetch of "details" * // * // Note: the fetch of "customer.contacts" and "customer.shippingAddress" * // are automatically included in the fetch of "customer" * }</pre> * <p> * You can use query() and lazy together on a single join. The query is executed * immediately and the lazy defines the batch size to use for further lazy * loading (if lazy loading is invoked). * </p> * <p> * <pre>{@code * * List<Order> list = * DB.find(Order.class) * .fetch("customer", new FetchConfig().query(10).lazy(5)) * .findList(); * * // query 1) find order * // query 2) find customer where id in (?,?,?,?,?,?,?,?,?,?) // first 10 customers * // .. then if lazy loading of customers is invoked * // .. use a batch size of 5 to load the customers * * }</pre> * <p> * <p> * Example of controlling the lazy loading query: * </p> * <p> * This gives us the ability to optimise the lazy loading query for a given use * case. * </p> * <p> * <pre>{@code * * List<Order> list = DB.find(Order.class) * .fetch("customer","name", new FetchConfig().lazy(5)) * .fetch("customer.contacts","contactName, phone, email") * .fetch("customer.shippingAddress") * .where().eq("status",Order.Status.NEW) * .findList(); * * // query 1) find order where status = Order.Status.NEW * // * // .. if lazy loading of customers is invoked * // .. use a batch size of 5 to load the customers * * }</pre> * * @author mario * @author rbygrave */
public class FetchConfig implements Serializable { private static final long serialVersionUID = 1L; private int lazyBatchSize = -1; private int queryBatchSize = -1; private boolean queryAll;
Construct the fetch configuration object.
/** * Construct the fetch configuration object. */
public FetchConfig() { }
Specify that this path should be lazy loaded using the default batch load size.
/** * Specify that this path should be lazy loaded using the default batch load * size. */
public FetchConfig lazy() { this.lazyBatchSize = 0; this.queryAll = false; return this; }
Specify that this path should be lazy loaded with a specified batch size.
Params:
  • lazyBatchSize – the batch size for lazy loading
/** * Specify that this path should be lazy loaded with a specified batch size. * * @param lazyBatchSize the batch size for lazy loading */
public FetchConfig lazy(int lazyBatchSize) { this.lazyBatchSize = lazyBatchSize; this.queryAll = false; return this; }
Eagerly fetch the beans in this path as a separate query (rather than as part of the main query).

This will use the default batch size for separate query which is 100.

/** * Eagerly fetch the beans in this path as a separate query (rather than as * part of the main query). * <p> * This will use the default batch size for separate query which is 100. * </p> */
public FetchConfig query() { this.queryBatchSize = 0; this.queryAll = true; return this; }
Eagerly fetch the beans in this path as a separate query (rather than as part of the main query).

The queryBatchSize is the number of parent id's that this separate query will load per batch.

This will load all beans on this path eagerly unless a lazy(int) is also used.

Params:
  • queryBatchSize – the batch size used to load beans on this path
/** * Eagerly fetch the beans in this path as a separate query (rather than as * part of the main query). * <p> * The queryBatchSize is the number of parent id's that this separate query * will load per batch. * </p> * <p> * This will load all beans on this path eagerly unless a {@link #lazy(int)} * is also used. * </p> * * @param queryBatchSize the batch size used to load beans on this path */
public FetchConfig query(int queryBatchSize) { this.queryBatchSize = queryBatchSize; // queryAll true as long as a lazy batch size has not already been set this.queryAll = (lazyBatchSize == -1); return this; }
Eagerly fetch the first batch of beans on this path. This is similar to query(int) but only fetches the first batch.

If there are more parent beans than the batch size then they will not be loaded eagerly but instead use lazy loading.

Params:
  • queryBatchSize – the number of parent beans this path is populated for
/** * Eagerly fetch the first batch of beans on this path. * This is similar to {@link #query(int)} but only fetches the first batch. * <p> * If there are more parent beans than the batch size then they will not be * loaded eagerly but instead use lazy loading. * </p> * * @param queryBatchSize the number of parent beans this path is populated for */
public FetchConfig queryFirst(int queryBatchSize) { this.queryBatchSize = queryBatchSize; this.queryAll = false; return this; }
Return the batch size for lazy loading.
/** * Return the batch size for lazy loading. */
public int getLazyBatchSize() { return lazyBatchSize; }
Return the batch size for separate query load.
/** * Return the batch size for separate query load. */
public int getQueryBatchSize() { return queryBatchSize; }
Return true if the query fetch should fetch 'all' rather than just the 'first' batch.
/** * Return true if the query fetch should fetch 'all' rather than just the * 'first' batch. */
public boolean isQueryAll() { return queryAll; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; FetchConfig that = (FetchConfig) o; if (lazyBatchSize != that.lazyBatchSize) return false; if (queryBatchSize != that.queryBatchSize) return false; return queryAll == that.queryAll; } @Override public int hashCode() { int result = lazyBatchSize; result = 92821 * result + queryBatchSize; result = 92821 * result + (queryAll ? 1 : 0); return result; } }