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  private boolean cache;
152
153  /**
154   * Construct the fetch configuration object.
155   */
156  public FetchConfig() {
157  }
158
159  /**
160   * Specify that this path should be lazy loaded using the default batch load
161   * size.
162   */
163  public FetchConfig lazy() {
164    this.lazyBatchSize = 0;
165    this.queryAll = false;
166    return this;
167  }
168
169  /**
170   * Specify that this path should be lazy loaded with a specified batch size.
171   *
172   * @param lazyBatchSize the batch size for lazy loading
173   */
174  public FetchConfig lazy(int lazyBatchSize) {
175    this.lazyBatchSize = lazyBatchSize;
176    this.queryAll = false;
177    return this;
178  }
179
180  /**
181   * Eagerly fetch the beans in this path as a separate query (rather than as
182   * part of the main query).
183   * <p>
184   * This will use the default batch size for separate query which is 100.
185   * </p>
186   */
187  public FetchConfig query() {
188    this.queryBatchSize = 0;
189    this.queryAll = true;
190    return this;
191  }
192
193  /**
194   * Eagerly fetch the beans fetching the beans from the L2 bean cache
195   * and using the DB for beans not in the cache.
196   */
197  public FetchConfig cache() {
198    this.cache = true;
199    this.queryBatchSize = 0;
200    this.queryAll = true;
201    return this;
202  }
203
204  /**
205   * Eagerly fetch the beans in this path as a separate query (rather than as
206   * part of the main query).
207   * <p>
208   * The queryBatchSize is the number of parent id's that this separate query
209   * will load per batch.
210   * </p>
211   * <p>
212   * This will load all beans on this path eagerly unless a {@link #lazy(int)}
213   * is also used.
214   * </p>
215   *
216   * @param queryBatchSize the batch size used to load beans on this path
217   */
218  public FetchConfig query(int queryBatchSize) {
219    this.queryBatchSize = queryBatchSize;
220    // queryAll true as long as a lazy batch size has not already been set
221    this.queryAll = (lazyBatchSize == -1);
222    return this;
223  }
224
225  /**
226   * Eagerly fetch the first batch of beans on this path.
227   * This is similar to {@link #query(int)} but only fetches the first batch.
228   * <p>
229   * If there are more parent beans than the batch size then they will not be
230   * loaded eagerly but instead use lazy loading.
231   * </p>
232   *
233   * @param queryBatchSize the number of parent beans this path is populated for
234   */
235  public FetchConfig queryFirst(int queryBatchSize) {
236    this.queryBatchSize = queryBatchSize;
237    this.queryAll = false;
238    return this;
239  }
240
241  /**
242   * Return the batch size for lazy loading.
243   */
244  public int getLazyBatchSize() {
245    return lazyBatchSize;
246  }
247
248  /**
249   * Return the batch size for separate query load.
250   */
251  public int getQueryBatchSize() {
252    return queryBatchSize;
253  }
254
255  /**
256   * Return true if the query fetch should fetch 'all' rather than just the
257   * 'first' batch.
258   */
259  public boolean isQueryAll() {
260    return queryAll;
261  }
262
263  /**
264   * Return true if this uses L2 bean cache.
265   */
266  public boolean isCache() {
267    return cache;
268  }
269
270  @Override
271  public boolean equals(Object o) {
272    if (this == o) return true;
273    if (o == null || getClass() != o.getClass()) return false;
274
275    FetchConfig that = (FetchConfig) o;
276    if (lazyBatchSize != that.lazyBatchSize) return false;
277    if (queryBatchSize != that.queryBatchSize) return false;
278    if (cache != that.cache) return false;
279    return queryAll == that.queryAll;
280  }
281
282  @Override
283  public int hashCode() {
284    int result = lazyBatchSize;
285    result = 92821 * result + queryBatchSize;
286    result = 92821 * result + (queryAll ? 1 : 0);
287    result = 92821 * result + (cache ? 1 : 0);
288    return result;
289  }
290}