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","contactName, phone, email") 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}