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}