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 /** 152 * Construct the fetch configuration object. 153 */ 154 public FetchConfig() { 155 } 156 157 /** 158 * Specify that this path should be lazy loaded using the default batch load 159 * size. 160 */ 161 public FetchConfig lazy() { 162 this.lazyBatchSize = 0; 163 this.queryAll = false; 164 return this; 165 } 166 167 /** 168 * Specify that this path should be lazy loaded with a specified batch size. 169 * 170 * @param lazyBatchSize 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 the batch size used to load beans on this path 204 */ 205 public FetchConfig query(int queryBatchSize) { 206 this.queryBatchSize = queryBatchSize; 207 // queryAll true as long as a lazy batch size has not already been set 208 this.queryAll = (lazyBatchSize == -1); 209 return this; 210 } 211 212 /** 213 * Eagerly fetch the first batch of beans on this path. 214 * This is similar to {@link #query(int)} but only fetches the first batch. 215 * <p> 216 * If there are more parent beans than the batch size then they will not be 217 * loaded eagerly but instead use lazy loading. 218 * </p> 219 * 220 * @param queryBatchSize the number of parent beans this path is populated for 221 */ 222 public FetchConfig queryFirst(int queryBatchSize) { 223 this.queryBatchSize = queryBatchSize; 224 this.queryAll = false; 225 return this; 226 } 227 228 /** 229 * Return the batch size for lazy loading. 230 */ 231 public int getLazyBatchSize() { 232 return lazyBatchSize; 233 } 234 235 /** 236 * Return the batch size for separate query load. 237 */ 238 public int getQueryBatchSize() { 239 return queryBatchSize; 240 } 241 242 /** 243 * Return true if the query fetch should fetch 'all' rather than just the 244 * 'first' batch. 245 */ 246 public boolean isQueryAll() { 247 return queryAll; 248 } 249 250 @Override 251 public boolean equals(Object o) { 252 if (this == o) return true; 253 if (o == null || getClass() != o.getClass()) return false; 254 255 FetchConfig that = (FetchConfig) o; 256 if (lazyBatchSize != that.lazyBatchSize) return false; 257 if (queryBatchSize != that.queryBatchSize) return false; 258 return queryAll == that.queryAll; 259 } 260 261 @Override 262 public int hashCode() { 263 int result = lazyBatchSize; 264 result = 92821 * result + queryBatchSize; 265 result = 92821 * result + (queryAll ? 1 : 0); 266 return result; 267 } 268}