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}