001package io.ebean;
002
003import java.io.Serializable;
004
005/**
006 * Defines the configuration options for a "query fetch" or a
007 * "lazy loading fetch". This gives you the ability to use multiple smaller
008 * queries to populate an object graph as opposed to a single large query.
009 * <p>
010 * The primary goal is to provide efficient ways of loading complex object
011 * graphs avoiding SQL Cartesian product and issues around populating object
012 * graphs that have multiple *ToMany relationships.
013 * </p>
014 * <p>
015 * It also provides the ability to control the lazy loading queries (batch size,
016 * selected properties and fetches) to avoid N+1 queries etc.
017 * <p>
018 * There can also be cases loading across a single OneToMany where 2 SQL queries
019 * using Ebean FetchConfig.query() can be more efficient than one SQL query.
020 * When the "One" side is wide (lots of columns) and the cardinality difference
021 * is high (a lot of "Many" beans per "One" bean) then this can be more
022 * efficient loaded as 2 SQL queries.
023 * </p>
024 * <p>
025 * <pre>{@code
026 * // Normal fetch join results in a single SQL query
027 * List<Order> list = DB.find(Order.class).fetch("details").findList();
028 *
029 * // Find Orders join details using a single SQL query
030 * }</pre>
031 * <p>
032 * Example: Using a "query join" instead of a "fetch join" we instead use 2 SQL queries
033 * </p>
034 * <p>
035 * <pre>{@code
036 *
037 * // This will use 2 SQL queries to build this object graph
038 * List<Order> list =
039 *     DB.find(Order.class)
040 *         .fetch("details", new FetchConfig().query())
041 *         .findList();
042 *
043 * // query 1) find order
044 * // query 2) find orderDetails where order.id in (?,?...) // first 100 order id's
045 *
046 * }</pre>
047 * <p>
048 * Example: Using 2 "query joins"
049 * </p>
050 * <p>
051 * <pre>{@code
052 *
053 * // This will use 3 SQL queries to build this object graph
054 * List<Order> list =
055 *     DB.find(Order.class)
056 *         .fetch("details", new FetchConfig().query())
057 *         .fetch("customer", new FetchConfig().queryFirst(5))
058 *         .findList();
059 *
060 * // query 1) find order
061 * // query 2) find orderDetails where order.id in (?,?...) // first 100 order id's
062 * // query 3) find customer where id in (?,?,?,?,?) // first 5 customers
063 *
064 * }</pre>
065 * <p>
066 * Example: Using "query joins" and partial objects
067 * </p>
068 * <p>
069 *
070 * <pre>{@code
071 * // This will use 3 SQL queries to build this object graph
072 * List<Order> list =
073 *     DB.find(Order.class)
074 *         .select("status, shipDate")
075 *         .fetch("details", "quantity, price", new FetchConfig().query())
076 *         .fetch("details.product", "sku, name")
077 *         .fetch("customer", "name", new FetchConfig().queryFirst(5))
078 *         .fetch("customer.contacts")
079 *         .fetch("customer.shippingAddress")
080 *         .findList();
081 *
082 * // query 1) find order (status, shipDate)
083 * // query 2) find orderDetail (quantity, price) fetch product (sku, name) where
084 * // order.id in (?,? ...)
085 * // query 3) find customer (name) fetch contacts (*) fetch shippingAddress (*)
086 * // where id in (?,?,?,?,?)
087 *
088 * // Note: the fetch of "details.product" is automatically included into the
089 * // fetch of "details"
090 * //
091 * // Note: the fetch of "customer.contacts" and "customer.shippingAddress"
092 * // are automatically included in the fetch of "customer"
093 * }</pre>
094 * <p>
095 * You can use query() and lazy together on a single join. The query is executed
096 * immediately and the lazy defines the batch size to use for further lazy
097 * loading (if lazy loading is invoked).
098 * </p>
099 * <p>
100 * <pre>{@code
101 *
102 * List<Order> list =
103 *     DB.find(Order.class)
104 *         .fetch("customer", new FetchConfig().query(10).lazy(5))
105 *         .findList();
106 *
107 * // query 1) find order
108 * // query 2) find customer where id in (?,?,?,?,?,?,?,?,?,?) // first 10 customers
109 * // .. then if lazy loading of customers is invoked
110 * // .. use a batch size of 5 to load the customers
111 *
112 * }</pre>
113 * <p>
114 * <p>
115 * Example of controlling the lazy loading query:
116 * </p>
117 * <p>
118 * This gives us the ability to optimise the lazy loading query for a given use
119 * case.
120 * </p>
121 * <p>
122 * <pre>{@code
123 *
124 * List<Order> list = DB.find(Order.class)
125 *   .fetch("customer","name", new FetchConfig().lazy(5))
126 *   .fetch("customer.contacts","contactName, phone, email")
127 *   .fetch("customer.shippingAddress")
128 *   .where().eq("status",Order.Status.NEW)
129 *   .findList();
130 *
131 * // query 1) find order where status = Order.Status.NEW
132 * //
133 * // .. if lazy loading of customers is invoked
134 * // .. use a batch size of 5 to load the customers
135 *
136 * }</pre>
137 *
138 * @author mario
139 * @author rbygrave
140 */
141public class FetchConfig implements Serializable {
142
143  private static final long serialVersionUID = 1L;
144
145  private int lazyBatchSize = -1;
146
147  private int queryBatchSize = -1;
148
149  private boolean queryAll;
150
151  /**
152   * Construct the fetch configuration object.
153   */
154  public FetchConfig() {
155  }
156
157  /**
158   * Specify that this path should be lazy loaded using the default batch load
159   * size.
160   */
161  public FetchConfig lazy() {
162    this.lazyBatchSize = 0;
163    this.queryAll = false;
164    return this;
165  }
166
167  /**
168   * Specify that this path should be lazy loaded with a specified batch size.
169   *
170   * @param lazyBatchSize the batch size for lazy loading
171   */
172  public FetchConfig lazy(int lazyBatchSize) {
173    this.lazyBatchSize = lazyBatchSize;
174    this.queryAll = false;
175    return this;
176  }
177
178  /**
179   * Eagerly fetch the beans in this path as a separate query (rather than as
180   * part of the main query).
181   * <p>
182   * This will use the default batch size for separate query which is 100.
183   * </p>
184   */
185  public FetchConfig query() {
186    this.queryBatchSize = 0;
187    this.queryAll = true;
188    return this;
189  }
190
191  /**
192   * Eagerly fetch the beans in this path as a separate query (rather than as
193   * part of the main query).
194   * <p>
195   * The queryBatchSize is the number of parent id's that this separate query
196   * will load per batch.
197   * </p>
198   * <p>
199   * This will load all beans on this path eagerly unless a {@link #lazy(int)}
200   * is also used.
201   * </p>
202   *
203   * @param queryBatchSize the batch size used to load beans on this path
204   */
205  public FetchConfig query(int queryBatchSize) {
206    this.queryBatchSize = queryBatchSize;
207    // queryAll true as long as a lazy batch size has not already been set
208    this.queryAll = (lazyBatchSize == -1);
209    return this;
210  }
211
212  /**
213   * Eagerly fetch the first batch of beans on this path.
214   * This is similar to {@link #query(int)} but only fetches the first batch.
215   * <p>
216   * If there are more parent beans than the batch size then they will not be
217   * loaded eagerly but instead use lazy loading.
218   * </p>
219   *
220   * @param queryBatchSize the number of parent beans this path is populated for
221   */
222  public FetchConfig queryFirst(int queryBatchSize) {
223    this.queryBatchSize = queryBatchSize;
224    this.queryAll = false;
225    return this;
226  }
227
228  /**
229   * Return the batch size for lazy loading.
230   */
231  public int getLazyBatchSize() {
232    return lazyBatchSize;
233  }
234
235  /**
236   * Return the batch size for separate query load.
237   */
238  public int getQueryBatchSize() {
239    return queryBatchSize;
240  }
241
242  /**
243   * Return true if the query fetch should fetch 'all' rather than just the
244   * 'first' batch.
245   */
246  public boolean isQueryAll() {
247    return queryAll;
248  }
249
250  @Override
251  public boolean equals(Object o) {
252    if (this == o) return true;
253    if (o == null || getClass() != o.getClass()) return false;
254
255    FetchConfig that = (FetchConfig) o;
256    if (lazyBatchSize != that.lazyBatchSize) return false;
257    if (queryBatchSize != that.queryBatchSize) return false;
258    return queryAll == that.queryAll;
259  }
260
261  @Override
262  public int hashCode() {
263    int result = lazyBatchSize;
264    result = 92821 * result + queryBatchSize;
265    result = 92821 * result + (queryAll ? 1 : 0);
266    return result;
267  }
268}