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