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