diff --git a/src/main/java/com/avaje/ebean/FetchConfig.java b/src/main/java/com/avaje/ebean/FetchConfig.java index a2ad44d30..db5527105 100644 --- a/src/main/java/com/avaje/ebean/FetchConfig.java +++ b/src/main/java/com/avaje/ebean/FetchConfig.java @@ -24,7 +24,7 @@ import java.io.Serializable; * *
  * // Normal fetch join results in a single SQL query
- * List<Order> list = Ebean.find(Order.class).join("details").findList();
+ * List<Order> list = Ebean.find(Order.class).fetch("details").findList();
  * 
  * // Find Orders join details using a single SQL query
  * 
@@ -51,8 +51,8 @@ import java.io.Serializable; * // This will use 3 SQL queries to build this object graph * List<Order> list = * Ebean.find(Order.class) - * .fetch("details", new JoinConfig().query()) - * .fetch("customer", new JoinConfig().query(5)) + * .fetch("details", new FetchConfig().query()) + * .fetch("customer", new FetchConfig().queryFirst(5)) * .findList(); * * // query 1) find order @@ -70,7 +70,7 @@ import java.io.Serializable; * .select("status, shipDate") * .fetch("details", "quantity, price", new FetchConfig().query()) * .fetch("details.product", "sku, name") - * .fetch("customer", "name", new FetchConfig().query(10)) + * .fetch("customer", "name", new FetchConfig().queryFirst(5)) * .fetch("customer.contacts") * .fetch("customer.shippingAddress") * .findList(); @@ -96,13 +96,13 @@ import java.io.Serializable; *
  * List<Order> list =
  *     Ebean.find(Order.class)
- *         .fetch("customer", new FetchConfig().query(3).lazy(10))
+ *         .fetch("customer", new FetchConfig().query(10).lazy(5))
  *         .findList();
  * 
  * // query 1) find order
- * // query 2) find customer where id in (?,?,?) // first 3 customers
+ * // query 2) find customer where id in (?,?,?,?,?,?,?,?,?,?) // first 10 customers
  * // .. then if lazy loading of customers is invoked
- * // .. use a batch size of 10 to load the customers
+ * // .. use a batch size of 5 to load the customers
  * 
  * 
* @@ -128,8 +128,8 @@ import java.io.Serializable; * // .. use a batch size of 5 to load the customers * * find customer (name) - * fetch contact (contactName, phone, email) - * fetch shippingAddress (*) + * fetch customer.contacts (contactName, phone, email) + * fetch customer.shippingAddress (*) * where id in (?,?,?,?,?) * * @@ -159,6 +159,7 @@ public class FetchConfig implements Serializable { */ public FetchConfig lazy() { this.lazyBatchSize = 0; + this.queryAll = false; return this; } @@ -170,11 +171,12 @@ public class FetchConfig implements Serializable { */ public FetchConfig lazy(int lazyBatchSize) { this.lazyBatchSize = lazyBatchSize; + this.queryAll = false; return this; } /** - * Specify that this path should be loaded as a separate query (rather than as + * Eagerly fetch the beans in this path as a separate query (rather than as * part of the main query). *

* This will use the default batch size for separate query which is 100. @@ -187,14 +189,15 @@ public class FetchConfig implements Serializable { } /** - * Specify that this path should be loaded as a separate query (rather than as + * Eagerly fetch the beans in this path as a separate query (rather than as * part of the main query). *

* The queryBatchSize is the number of parent id's that this separate query * will load per batch. *

*

- * This will load all beans on this path eagerly. + * This will load all beans on this path eagerly unless a {@link #lazy(int)} + * is also used. *

* * @param queryBatchSize @@ -202,12 +205,14 @@ public class FetchConfig implements Serializable { */ public FetchConfig query(int queryBatchSize) { this.queryBatchSize = queryBatchSize; - this.queryAll = true; + // queryAll true as long as a lazy batch size has not already been set + this.queryAll = (lazyBatchSize == -1); return this; } /** - * Similar to {@link #query(int)} but only fetches the first batch. + * Eagerly fetch the first batch of beans on this path. + * This is similar to {@link #query(int)} but only fetches the first batch. *

* If there are more parent beans than the batch size then they will not be * loaded eagerly but instead use lazy loading. @@ -228,7 +233,7 @@ public class FetchConfig implements Serializable { public int getLazyBatchSize() { return lazyBatchSize; } - + /** * Return the batch size for separate query load. */ diff --git a/src/main/java/com/avaje/ebean/OrderBy.java b/src/main/java/com/avaje/ebean/OrderBy.java index 38da01765..734960217 100644 --- a/src/main/java/com/avaje/ebean/OrderBy.java +++ b/src/main/java/com/avaje/ebean/OrderBy.java @@ -32,6 +32,10 @@ public final class OrderBy implements Serializable { public OrderBy() { this.list = new ArrayList(2); } + + private OrderBy(List list) { + this.list = list; + } /** * Create an orderBy parsing the order by clause. @@ -81,6 +85,17 @@ public final class OrderBy implements Serializable { return query; } + /** + * Return a copy of this OrderBy with the path trimmed. + */ + public OrderBy copyWithTrim(String path) { + List newList = new ArrayList(list.size()); + for (int i = 0; i < list.size(); i++) { + newList.add(list.get(i).copyWithTrim(path)); + } + return new OrderBy(newList); + } + /** * Return the properties for this OrderBy. */ @@ -196,6 +211,13 @@ public final class OrderBy implements Serializable { this.ascending = ascending; } + /** + * Return a copy of this Property with the path trimmed. + */ + public Property copyWithTrim(String path) { + return new Property(property.substring(path.length() + 1), ascending); + } + protected int hash() { int hc = property.hashCode(); hc = hc * 31 + (ascending ? 0 : 1); diff --git a/src/main/java/com/avaje/ebeaninternal/api/LoadBeanBuffer.java b/src/main/java/com/avaje/ebeaninternal/api/LoadBeanBuffer.java new file mode 100644 index 000000000..f33c2181b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/LoadBeanBuffer.java @@ -0,0 +1,24 @@ +package com.avaje.ebeaninternal.api; + +import java.util.List; + +import com.avaje.ebean.bean.EntityBeanIntercept; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +/** + * A buffer of beans for batch lazy loading and secondary query loading. + */ +public interface LoadBeanBuffer { + + public List getBatch(); + + public BeanDescriptor getBeanDescriptor(); + + public PersistenceContext getPersistenceContext(); + + public String getFullPath(); + + public void configureQuery(SpiQuery query, String lazyLoadProperty); + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/api/LoadBeanContext.java b/src/main/java/com/avaje/ebeaninternal/api/LoadBeanContext.java index e5d711260..cb99e4ff2 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/LoadBeanContext.java +++ b/src/main/java/com/avaje/ebeaninternal/api/LoadBeanContext.java @@ -1,39 +1,10 @@ package com.avaje.ebeaninternal.api; -import com.avaje.ebean.bean.PersistenceContext; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; /** * Controls the loading of ManyToOne and OneToOne relationships. - * - * @author rbygrave */ public interface LoadBeanContext extends LoadSecondaryQuery { - /** - * Configure the query to load beans for this node/path. - */ - public void configureQuery(SpiQuery query, String lazyLoadProperty); - - /** - * Return the full path of this node from the root object. - */ - public String getFullPath(); - - /** - * Return the persistence context used for all queries - * related to this object graph. - */ - public PersistenceContext getPersistenceContext(); - - /** - * Return the BeanDescriptor for beans for this node. - */ - public BeanDescriptor getBeanDescriptor(); - - /** - * Return the batchSize used for lazy loading beans. - */ - public int getBatchSize(); } diff --git a/src/main/java/com/avaje/ebeaninternal/api/LoadBeanRequest.java b/src/main/java/com/avaje/ebeaninternal/api/LoadBeanRequest.java index ab4c94c2b..f5a8006d7 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/LoadBeanRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/api/LoadBeanRequest.java @@ -9,55 +9,52 @@ import com.avaje.ebean.bean.EntityBeanIntercept; * Request for loading ManyToOne and OneToOne relationships. */ public class LoadBeanRequest extends LoadRequest { - - private final List batch; - private final LoadBeanContext loadContext; - - private final String lazyLoadProperty; - - private final boolean loadCache; - - public LoadBeanRequest(LoadBeanContext loadContext, List batch, - Transaction transaction, int batchSize, boolean lazy, String lazyLoadProperty, boolean loadCache) { - - super(transaction, batchSize, lazy); - this.loadContext = loadContext; - this.batch = batch; - this.lazyLoadProperty = lazyLoadProperty; - this.loadCache = loadCache; - } - - public boolean isLoadCache() { - return loadCache; - } + private final List batch; - public String getDescription() { - String fullPath = loadContext.getFullPath(); - String s = "path:" + fullPath + " batch:" + batchSize + " actual:" - + batch.size(); - return s; - } + private final LoadBeanBuffer LoadBuffer; - /** - * Return the batch of beans to actually load. - */ - public List getBatch() { - return batch; - } + private final String lazyLoadProperty; - /** - * Return the load context. - */ - public LoadBeanContext getLoadContext() { - return loadContext; - } + private final boolean loadCache; + + public LoadBeanRequest(LoadBeanBuffer LoadBuffer, Transaction transaction, boolean lazy, String lazyLoadProperty, + boolean loadCache) { + + super(transaction, lazy); + this.LoadBuffer = LoadBuffer; + this.batch = LoadBuffer.getBatch(); + this.lazyLoadProperty = lazyLoadProperty; + this.loadCache = loadCache; + } + + public boolean isLoadCache() { + return loadCache; + } + + public String getDescription() { + return "path:" + LoadBuffer.getFullPath() + " batch:" + batch.size(); + } + + /** + * Return the batch of beans to actually load. + */ + public List getBatch() { + return batch; + } + + /** + * Return the load context. + */ + public LoadBeanBuffer getLoadContext() { + return LoadBuffer; + } + + /** + * Return the property that invoked the lazy loading. + */ + public String getLazyLoadProperty() { + return lazyLoadProperty; + } - /** - * Return the property that invoked the lazy loading. - */ - public String getLazyLoadProperty() { - return lazyLoadProperty; - } - } diff --git a/src/main/java/com/avaje/ebeaninternal/api/LoadManyBuffer.java b/src/main/java/com/avaje/ebeaninternal/api/LoadManyBuffer.java new file mode 100644 index 000000000..75f1b9112 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/LoadManyBuffer.java @@ -0,0 +1,30 @@ +package com.avaje.ebeaninternal.api; + +import java.util.List; + +import com.avaje.ebean.bean.BeanCollection; +import com.avaje.ebean.bean.ObjectGraphNode; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; + +/** + * A buffer of bean collections for batch lazy loading and secondary query loading. + */ +public interface LoadManyBuffer { + + public List> getBatch(); + + public BeanPropertyAssocMany getBeanProperty(); + + public ObjectGraphNode getObjectGraphNode(); + + public BeanDescriptor getBeanDescriptor(); + + public PersistenceContext getPersistenceContext(); + + public String getFullPath(); + + public void configureQuery(SpiQuery query); + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/api/LoadManyContext.java b/src/main/java/com/avaje/ebeaninternal/api/LoadManyContext.java index 543bde12c..72ff7b107 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/LoadManyContext.java +++ b/src/main/java/com/avaje/ebeaninternal/api/LoadManyContext.java @@ -1,54 +1,9 @@ package com.avaje.ebeaninternal.api; -import com.avaje.ebean.bean.ObjectGraphNode; -import com.avaje.ebean.bean.PersistenceContext; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; /** * Controls the loading of OneToMany and ManyToMany relationships. - * - * @author rbygrave */ public interface LoadManyContext extends LoadSecondaryQuery { - - /** - * Configure the query to load beans for this node/path. - */ - public void configureQuery(SpiQuery query); - - /** - * Return the full path of this node from the root object. - */ - public String getFullPath(); - - /** - * Return the node location for this node/path. - */ - public ObjectGraphNode getObjectGraphNode(); - - - /** - * Return the persistence context used for all queries - * related to this object graph. - */ - public PersistenceContext getPersistenceContext(); - - /** - * Return the batchSize used for lazy loading beans. - */ - public int getBatchSize(); - - /** - * Return the BeanDescriptor for beans for this node. - */ - public BeanDescriptor getBeanDescriptor(); - - /** - * Return the associated Many bean property. - */ - public BeanPropertyAssocMany getBeanProperty(); - - } diff --git a/src/main/java/com/avaje/ebeaninternal/api/LoadManyRequest.java b/src/main/java/com/avaje/ebeaninternal/api/LoadManyRequest.java index 1bbbaf51b..e053a1311 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/LoadManyRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/api/LoadManyRequest.java @@ -13,28 +13,24 @@ public class LoadManyRequest extends LoadRequest { private final List> batch; - private final LoadManyContext loadContext; + private final LoadManyBuffer loadContext; private final boolean onlyIds; private final boolean loadCache; - public LoadManyRequest(LoadManyContext loadContext, - List> batch, Transaction transaction, - int batchSize, boolean lazy, boolean onlyIds, boolean loadCache) { + public LoadManyRequest(LoadManyBuffer loadContext, Transaction transaction, int batchSize, boolean lazy, + boolean onlyIds, boolean loadCache) { - super(transaction, batchSize, lazy); + super(transaction, lazy); this.loadContext = loadContext; - this.batch = batch; + this.batch = loadContext.getBatch(); this.onlyIds = onlyIds; this.loadCache = loadCache; } public String getDescription() { - String fullPath = loadContext.getFullPath(); - String s = "path:" + fullPath + " batch:" + batchSize + " actual:" - + batch.size(); - return s; + return "path:" + loadContext.getFullPath() + " size:"+ batch.size(); } /** @@ -47,7 +43,7 @@ public class LoadManyRequest extends LoadRequest { /** * Return the load context. */ - public LoadManyContext getLoadContext() { + public LoadManyBuffer getLoadContext() { return loadContext; } diff --git a/src/main/java/com/avaje/ebeaninternal/api/LoadRequest.java b/src/main/java/com/avaje/ebeaninternal/api/LoadRequest.java index 23dc372e5..2364ce71e 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/LoadRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/api/LoadRequest.java @@ -9,18 +9,14 @@ public abstract class LoadRequest { protected final boolean lazy; - protected final int batchSize; - protected final Transaction transaction; - public LoadRequest(Transaction transaction, int batchSize, boolean lazy) { + public LoadRequest(Transaction transaction, boolean lazy) { this.transaction = transaction; - this.batchSize = batchSize; this.lazy = lazy; } - /** * Return true if this is a lazy load and false if it is a secondary query. */ @@ -28,13 +24,6 @@ public abstract class LoadRequest { return lazy; } - /** - * Return the requested batch size. - */ - public int getBatchSize() { - return batchSize; - } - /** * Return the transaction to use if this is a secondary query. *

diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionList.java b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionList.java index e1a9fb49c..52190c5e0 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionList.java @@ -18,10 +18,10 @@ public interface SpiExpressionList extends ExpressionList { */ public List getUnderlyingList(); - /** - * Trim the path for filterMany() expressions. - */ - public void trimPath(int prefixTrim); + /** + * Return a copy of the ExpressionList with the path trimmed for filterMany() expressions. + */ + public SpiExpressionList trimPath(int prefixTrim); /** * Restore the ExpressionFactory after deserialisation. diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultBeanLoader.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultBeanLoader.java index 0bd05ca18..3a4677646 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultBeanLoader.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultBeanLoader.java @@ -15,10 +15,10 @@ import com.avaje.ebean.bean.EntityBean; import com.avaje.ebean.bean.EntityBeanIntercept; import com.avaje.ebean.bean.ObjectGraphNode; import com.avaje.ebean.bean.PersistenceContext; -import com.avaje.ebeaninternal.api.LoadBeanContext; +import com.avaje.ebeaninternal.api.LoadBeanBuffer; import com.avaje.ebeaninternal.api.LoadBeanRequest; -import com.avaje.ebeaninternal.api.LoadManyContext; import com.avaje.ebeaninternal.api.LoadManyRequest; +import com.avaje.ebeaninternal.api.LoadManyBuffer; import com.avaje.ebeaninternal.api.SpiQuery; import com.avaje.ebeaninternal.api.SpiQuery.Mode; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; @@ -27,8 +27,6 @@ import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext; /** * Helper to handle lazy loading and refreshing of beans. - * - * @author rbygrave */ public class DefaultBeanLoader { @@ -50,32 +48,29 @@ public class DefaultBeanLoader { * re-use the query plan cache and get DB statement re-use. *

*/ - private int getBatchSize(int batchListSize, int requestedBatchSize) { - if (batchListSize == requestedBatchSize) { - return batchListSize; - } - if (batchListSize == 1) { + private int getBatchSize(int batchSize) { + + if (batchSize == 1) { // there is only one bean/collection to load return 1; } - if (requestedBatchSize <= 5) { + if (batchSize <= 5) { // anything less than 5 becomes 5 return 5; } - if (batchListSize <= 10 || requestedBatchSize <= 10) { - // 10 or less to load - // ... or we wanted a batch size between 6 and 10 + if (batchSize <= 10) { return 10; } - if (batchListSize <= 20 || requestedBatchSize <= 20) { - // 20 or less to load - // ... or we wanted a batch size between 11 and 20 + if (batchSize <= 20) { return 20; } - if (batchListSize <= 50) { + if (batchSize <= 50) { return 50; } - return requestedBatchSize; + if (batchSize <= 100) { + return 100; + } + return batchSize; } public void refreshMany(Object parentBean, String propertyName) { @@ -86,9 +81,9 @@ public class DefaultBeanLoader { List> batch = loadRequest.getBatch(); - int batchSize = getBatchSize(batch.size(), loadRequest.getBatchSize()); + int batchSize = getBatchSize(batch.size()); - LoadManyContext ctx = loadRequest.getLoadContext(); + LoadManyBuffer ctx = loadRequest.getLoadContext(); BeanPropertyAssocMany many = ctx.getBeanProperty(); PersistenceContext pc = ctx.getPersistenceContext(); @@ -154,14 +149,14 @@ public class DefaultBeanLoader { } } - public void loadMany(BeanCollection bc, LoadManyContext ctx, boolean onlyIds) { + public void loadMany(BeanCollection bc, boolean onlyIds) { Object parentBean = bc.getOwnerBean(); String propertyName = bc.getPropertyName(); - ObjectGraphNode node = ctx == null ? null : ctx.getObjectGraphNode(); + //ObjectGraphNode node = ctx == null ? null : ctx.getObjectGraphNode(); - loadManyInternal(parentBean, propertyName, null, false, node, onlyIds); + loadManyInternal(parentBean, propertyName, null, false, null, onlyIds); } public void refreshMany(Object parentBean, String propertyName, Transaction t) { @@ -170,17 +165,15 @@ public class DefaultBeanLoader { private void loadManyInternal(Object parentBean, String propertyName, Transaction t, boolean refresh, ObjectGraphNode node, boolean onlyIds) { - EntityBeanIntercept ebi = null; - PersistenceContext pc = null; - BeanCollection beanCollection = null; - ExpressionList filterMany = null; - - ebi = ((EntityBean) parentBean)._ebean_getIntercept(); - pc = ebi.getPersistenceContext(); + EntityBeanIntercept ebi = ((EntityBean) parentBean)._ebean_getIntercept(); + PersistenceContext pc = ebi.getPersistenceContext(); BeanDescriptor parentDesc = server.getBeanDescriptor(parentBean.getClass()); BeanPropertyAssocMany many = (BeanPropertyAssocMany) parentDesc.getBeanProperty(propertyName); + BeanCollection beanCollection = null; + ExpressionList filterMany = null; + Object currentValue = many.getValue(parentBean); if (currentValue instanceof BeanCollection) { beanCollection = (BeanCollection) currentValue; @@ -270,9 +263,9 @@ public class DefaultBeanLoader { throw new RuntimeException("Nothing in batch?"); } - int batchSize = getBatchSize(batch.size(), loadRequest.getBatchSize()); + int batchSize = getBatchSize(batch.size()); - LoadBeanContext ctx = loadRequest.getLoadContext(); + LoadBeanBuffer ctx = loadRequest.getLoadContext(); BeanDescriptor desc = ctx.getBeanDescriptor(); Class beanType = desc.getBeanType(); @@ -349,7 +342,6 @@ public class DefaultBeanLoader { ebis[i].setLazyLoadFailure(); } } - } public void refresh(Object bean) { @@ -362,7 +354,6 @@ public class DefaultBeanLoader { private void refreshBeanInternal(Object bean, SpiQuery.Mode mode) { - EntityBeanIntercept ebi = ((EntityBean) bean)._ebean_getIntercept();; PersistenceContext pc = ebi.getPersistenceContext(); diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java index ab9a46d53..d4db15306 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -155,7 +155,7 @@ public final class DefaultServer implements SpiEbeanServer { private final CQueryEngine cqueryEngine; - @Deprecated + //@Deprecated private DdlGenerator ddlGenerator; private final ExpressionFactory expressionFactory; @@ -188,14 +188,16 @@ public final class DefaultServer implements SpiEbeanServer { */ private int lazyLoadBatchSize; - /** The query batch size */ + /** + * The query batch size + */ private int queryBatchSize; + /** * JDBC driver specific handling for JDBC batch execution. */ private PstmtBatch pstmtBatch; - /** * holds plugins (e.g. ddl generator) detected by the service loader */ @@ -502,7 +504,7 @@ public final class DefaultServer implements SpiEbeanServer { public void loadMany(BeanCollection bc, boolean onlyIds) { - beanLoader.loadMany(bc, null, onlyIds); + beanLoader.loadMany(bc, onlyIds); } public void refresh(Object bean) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/FilterExprPath.java b/src/main/java/com/avaje/ebeaninternal/server/expression/FilterExprPath.java index 3ef049285..84c2ba26b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/FilterExprPath.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/FilterExprPath.java @@ -5,38 +5,36 @@ import java.io.Serializable; /** * This is the path prefix for filterMany. *

- * The actual path can change due to FetchConfig query joins that proceed - * the query that includes the filterMany. + * The actual path can change due to FetchConfig query joins that proceed the + * query that includes the filterMany. *

- * - * @author rbygrave */ public class FilterExprPath implements Serializable { - private static final long serialVersionUID = -6420905565372842018L; - - /** - * The path of the filterMany. - */ - private String path; - - public FilterExprPath(String path){ - this.path = path; - } - - /** - * Trim off leading part of the path due to a - * proceeding (earlier) query join etc. - */ - public void trimPath(int prefixTrim) { - path = path.substring(prefixTrim); - } + private static final long serialVersionUID = -6420905565372842018L; - /** - * Return the path. This is a prefix used in the filterMany expressions. - */ - public String getPath() { - return path; - } + /** + * The path of the filterMany. + */ + private String path; + + public FilterExprPath(String path) { + this.path = path; + } + + /** + * Return a copy of the FilterExprPath trimming off leading part of the path + * due to a proceeding (earlier) query join etc. + */ + public FilterExprPath trimPath(int prefixTrim) { + return new FilterExprPath(path.substring(prefixTrim)); + } + + /** + * Return the path. This is a prefix used in the filterMany expressions. + */ + public String getPath() { + return path; + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBaseContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBaseContext.java new file mode 100644 index 000000000..674339a27 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBaseContext.java @@ -0,0 +1,98 @@ +package com.avaje.ebeaninternal.server.loadcontext; + +import com.avaje.ebean.FetchConfig; +import com.avaje.ebean.bean.ObjectGraphNode; +import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; + +/** + * Base class for Bean and BeanCollection loading (lazy loading and query join loading). + */ +public abstract class DLoadBaseContext { + + protected final DLoadContext parent; + + protected final BeanDescriptor desc; + + protected final String path; + + protected final String fullPath; + + protected final OrmQueryProperties queryProps; + + protected final boolean hitCache; + + protected final String serverName; + + protected final int firstBatchSize; + + protected final int secondaryBatchSize; + + protected final ObjectGraphNode objectGraphNode; + + protected final boolean queryFetch; + + + public DLoadBaseContext(DLoadContext parent, BeanDescriptor desc, String path, int defaultBatchSize, OrmQueryProperties queryProps) { + + this.parent = parent; + this.serverName = parent.getEbeanServer().getName(); + this.desc = desc; + this.queryProps = queryProps; + this.path = path; + this.fullPath = parent.getFullPath(path); + + this.hitCache = !parent.isExcludeBeanCache() && desc.isBeanCaching(); + + this.objectGraphNode = parent.getObjectGraphNode(path); + + this.queryFetch = queryProps != null && queryProps.isQueryFetch(); + this.firstBatchSize = initFirstBatchSize(defaultBatchSize, queryProps); + this.secondaryBatchSize = initSecondaryBatchSize(defaultBatchSize, firstBatchSize, queryProps); + } + + private int initFirstBatchSize(int batchSize, OrmQueryProperties queryProps) { + if (queryProps == null) { + return batchSize; + } + FetchConfig fetchConfig = queryProps.getFetchConfig(); + if (fetchConfig == null) { + return batchSize; + } + + int queryBatchSize = fetchConfig.getQueryBatchSize(); + if (queryBatchSize == -1) { + // not eager query fetch, just lazy loading + return batchSize; + + } else if (queryBatchSize == 0) { + // default query fetch batch size is 100 + return 100; + + } else { + return queryBatchSize; + } + } + + private int initSecondaryBatchSize(int defaultBatchSize, int firstBatchSize, OrmQueryProperties queryProps) { + if (queryProps == null) { + return defaultBatchSize; + } + FetchConfig fetchConfig = queryProps.getFetchConfig(); + if (fetchConfig == null) { + return defaultBatchSize; + } + if (fetchConfig.isQueryAll()) { + return firstBatchSize; + } + + int lazyBatchSize = fetchConfig.getLazyBatchSize(); + return (lazyBatchSize > 1) ? lazyBatchSize : defaultBatchSize; + } + + protected PersistenceContext getPersistenceContext() { + return parent.getPersistenceContext(); + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java index a85c5a0c2..cd6d7264b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java @@ -1,69 +1,44 @@ package com.avaje.ebeaninternal.server.loadcontext; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import com.avaje.ebean.bean.BeanLoader; -import com.avaje.ebean.bean.EntityBean; import com.avaje.ebean.bean.EntityBeanIntercept; -import com.avaje.ebean.bean.ObjectGraphNode; import com.avaje.ebean.bean.PersistenceContext; +import com.avaje.ebeaninternal.api.LoadBeanBuffer; import com.avaje.ebeaninternal.api.LoadBeanContext; import com.avaje.ebeaninternal.api.LoadBeanRequest; -import com.avaje.ebeaninternal.api.LoadContext; import com.avaje.ebeaninternal.api.SpiQuery; import com.avaje.ebeaninternal.server.core.OrmQueryRequest; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Default implementation of LoadBeanContext. - * */ -public class DLoadBeanContext implements LoadBeanContext, BeanLoader { - - private static final Logger logger = LoggerFactory.getLogger(DLoadBeanContext.class); +public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext{ - protected final DLoadContext parent; + private List bufferList; + + private LoadBuffer currentBuffer; - protected final BeanDescriptor desc; - - protected final String path; - - protected final String fullPath; - - private final DLoadList weakList; - - private final OrmQueryProperties queryProps; - - private int batchSize; - - public DLoadBeanContext(DLoadContext parent, BeanDescriptor desc, String path, int batchSize, - OrmQueryProperties queryProps, DLoadList weakList) { + public DLoadBeanContext(DLoadContext parent, BeanDescriptor desc, String path, int defaultBatchSize, OrmQueryProperties queryProps) { - this.parent = parent; - this.desc = desc; - this.path = path; - this.batchSize = batchSize; - this.queryProps = queryProps; - this.weakList = weakList; - - if (parent.getRelativePath() == null) { - this.fullPath = path; - } else { - this.fullPath = parent.getRelativePath() + "." + path; - } + super(parent, desc, path, defaultBatchSize, queryProps); + + this.currentBuffer = createBuffer(firstBatchSize); + this.bufferList = queryFetch ? new ArrayList() : null; } - - public void configureQuery(SpiQuery query, String lazyLoadProperty) { + + protected void configureQuery(SpiQuery query, String lazyLoadProperty) { // propagate the readOnly state if (parent.isReadOnly() != null) { query.setReadOnly(parent.isReadOnly()); } - query.setParentNode(getObjectGraphNode()); + query.setParentNode(objectGraphNode); query.setLazyLoadProperty(lazyLoadProperty); if (queryProps != null) { @@ -74,181 +49,131 @@ public class DLoadBeanContext implements LoadBeanContext, BeanLoader { } } - public String getFullPath() { - return fullPath; - } - - public PersistenceContext getPersistenceContext() { - return parent.getPersistenceContext(); - } + protected void register(EntityBeanIntercept ebi){ - public OrmQueryProperties getQueryProps() { - return queryProps; - } - - public ObjectGraphNode getObjectGraphNode() { - return parent.getObjectGraphNode(path); - } - - public String getPath() { - return path; - } - - public String getName() { - return parent.getEbeanServer().getName(); - } - - public int getBatchSize() { - return batchSize; - } - - public void setBatchSize(int batchSize) { - this.batchSize = batchSize; - } - - public BeanDescriptor getBeanDescriptor() { - return desc; - } - - public LoadContext getGraphContext() { - return parent; - } - - public void register(EntityBeanIntercept ebi){ - int pos = weakList.add(ebi); - ebi.setBeanLoader(pos, this, parent.getPersistenceContext()); - } - - /** - * Check if we can load the bean from L2 cache. If so avoid loading from the DB. - */ - private boolean loadBeanFromCache(EntityBeanIntercept ebi, int position) { - - if (!desc.loadFromCache(ebi)) { - return false; - } - // we loaded the bean from cache - weakList.removeEntry(position); - if (logger.isTraceEnabled()) { - logger.trace("Loading path:" + fullPath + " - bean loaded from L2 cache, position[" + position + "]"); + ebi.setBeanLoader(0, currentBuffer, getPersistenceContext()); + if (currentBuffer.add(ebi)) { + // the currentBuffer is full so create another one + currentBuffer = createBuffer(secondaryBatchSize); } - return true; - } - - /** - * Load this bean and potentially a batch of similar beans. - */ - public void loadBean(EntityBeanIntercept ebi) { - - // A synchronized (this) is effectively held by EntityBeanIntercept.loadBean() - - if (desc.lazyLoadMany(ebi)) { - // lazy load property was a Many - return; - } - - int position = ebi.getBeanLoaderIndex(); - boolean hitCache = !parent.isExcludeBeanCache() && desc.isBeanCaching(); - - if (hitCache && loadBeanFromCache(ebi, position)) { - // successfully hit the L2 cache so don't invoke DB lazy loading - return; - } - - // Get a batch of beans to lazy load - List batch = null; - try { - batch = weakList.getLoadBatch(position, batchSize); - } catch (IllegalStateException e) { - logger.error("type[" + desc.getFullName() + "] fullPath[" + fullPath + "] batchSize[" + batchSize + "]", e); - } - - if (hitCache && batchSize > 1) { - // Check each of the beans in the batch to see if they are in the L2 cache. - // Add more as necessary to make up our batch that will be loaded. - batch = loadBeanCheckBatch(batch); - } - - if (logger.isTraceEnabled()) { - for (int i = 0; i < batch.size(); i++) { - - EntityBeanIntercept entityBeanIntercept = batch.get(i); - EntityBean owner = entityBeanIntercept.getOwner(); - Object id = desc.getId(owner); - - logger.trace("LoadBean type["+owner.getClass().getName()+"] fullPath["+fullPath+"] id["+id+"] batchIndex["+i+"] beanLoaderIndex["+entityBeanIntercept.getBeanLoaderIndex()+"]"); - } - } - - LoadBeanRequest req = new LoadBeanRequest(this, batch, null, batchSize, true, ebi.getLazyLoadProperty(), hitCache); - parent.getEbeanServer().loadBean(req); + } - } - - /** - * Check each of the beans in the batch to see if they are in the cache. - * Get more beans out as necessary to get our desired batch size. - */ - private List loadBeanCheckBatch(List batch) { - - - List actualLoadBatch = new ArrayList(batchSize); - List batchToCheck = batch; - - int loadedFromCache = 0; - - while (true) { - // check each bean (not already checked) to see if it is in the cache - for (int i = 0; i < batchToCheck.size(); i++) { - if (!desc.loadFromCache(batchToCheck.get(i))) { - actualLoadBatch.add(batchToCheck.get(i)); - } else { - loadedFromCache++; - if (logger.isTraceEnabled()) { - logger.trace( "Loading path:" + fullPath + " - bean loaded from L2 cache(batch)"); - } - } - } - - if (batchToCheck.isEmpty()) { - // we have exhausted all the beans that need lazy loading - break; - } - int more = batchSize - actualLoadBatch.size(); - if (more <= 0 || loadedFromCache > 500) { - break; - } - // get some more to check as we loaded some from L2 cache - batchToCheck = weakList.getNextBatch(more); - } - return actualLoadBatch; - } + private LoadBuffer createBuffer(int size) { + LoadBuffer buffer = new LoadBuffer(this, size); + if (bufferList != null) { + bufferList.add(buffer); + } + return buffer; + } public void loadSecondaryQuery(OrmQueryRequest parentRequest, int requestedBatchSize, boolean all) { + if (!queryFetch) { + throw new IllegalStateException("Not expecting loadSecondaryQuery() to be called?"); + } synchronized (this) { - do { - List batch = weakList.getNextBatch(requestedBatchSize); - if (batch.size() == 0) { - // there are no beans to load - if (logger.isTraceEnabled()) { - logger.trace("Loading path:" + fullPath + " - no more beans to load"); + + if (bufferList != null) { + for (LoadBuffer loadBuffer : bufferList) { + if (!loadBuffer.list.isEmpty()) { + boolean loadCache = false; + LoadBeanRequest req = new LoadBeanRequest(loadBuffer, parentRequest.getTransaction(), false, null, loadCache); + + parent.getEbeanServer().loadBean(req); + if (!queryProps.isQueryFetchAll()) { + // Stop - only fetch the first batch ... the rest will be lazy loaded + break; + } } - return; + // this is only run once - secondary query is a one shot deal + this.bufferList = null; } - boolean loadCache = false; - LoadBeanRequest req = new LoadBeanRequest(this, batch, parentRequest.getTransaction(), requestedBatchSize, false, null, loadCache); + } + } + } + - if (logger.isTraceEnabled()) { - logger.trace("Loading path:" + fullPath + " - secondary query batch load [" + batch.size() + "] beans"); + /** + * A buffer for batch loading beans on a given path. + */ + public static class LoadBuffer implements BeanLoader, LoadBeanBuffer { + + private final DLoadBeanContext context; + private final int batchSize; + private final List list; + + public LoadBuffer(DLoadBeanContext context, int batchSize) { + this.context = context; + this.batchSize = batchSize; + this.list = new ArrayList(batchSize); + } + + /** + * Return true if the buffer is full. + */ + public boolean add(EntityBeanIntercept ebi) { + list.add(ebi); + return batchSize == list.size(); + } + + @Override + public List getBatch() { + return list; + } + + @Override + public String getName() { + return context.serverName; + } + + @Override + public BeanDescriptor getBeanDescriptor() { + return context.desc; + } + + @Override + public PersistenceContext getPersistenceContext() { + return context.getPersistenceContext(); + } + + @Override + public void configureQuery(SpiQuery query, String lazyLoadProperty) { + context.configureQuery(query, lazyLoadProperty); + } + + @Override + public void loadBean(EntityBeanIntercept ebi) { + // A synchronized (this) is effectively held by EntityBeanIntercept.loadBean() + + if (context.desc.lazyLoadMany(ebi)) { + // lazy load property was a Many + return; + } + + if (context.hitCache && context.desc.loadFromCache(ebi)) { + // successfully hit the L2 cache so don't invoke DB lazy loading + list.remove(ebi); + return; + } + + if (context.hitCache) { + // Check each of the beans in the batch to see if they are in the L2 cache. + Iterator iterator = list.iterator(); + while (iterator.hasNext()) { + EntityBeanIntercept bean = iterator.next(); + if (context.desc.loadFromCache(bean)) { + iterator.remove(); + } } + } - parent.getEbeanServer().loadBean(req); - if (!all) { - break; - } + LoadBeanRequest req = new LoadBeanRequest(this, null, true, ebi.getLazyLoadProperty(), context.hitCache); + context.desc.getEbeanServer().loadBean(req); + } - } while (true); + @Override + public String getFullPath() { + return context.fullPath; } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java index e26e5660e..71a7e2189 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java @@ -9,7 +9,6 @@ import com.avaje.ebean.bean.EntityBeanIntercept; import com.avaje.ebean.bean.ObjectGraphNode; import com.avaje.ebean.bean.ObjectGraphOrigin; import com.avaje.ebean.bean.PersistenceContext; -import com.avaje.ebean.config.GlobalProperties; import com.avaje.ebeaninternal.api.LoadContext; import com.avaje.ebeaninternal.api.LoadSecondaryQuery; import com.avaje.ebeaninternal.api.SpiEbeanServer; @@ -24,8 +23,6 @@ import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; /** * Default implementation of LoadContext. - * - * @author rbygrave */ public class DLoadContext implements LoadContext { @@ -48,7 +45,6 @@ public class DLoadContext implements LoadContext { private final String relativePath; private final ObjectGraphOrigin origin; private final boolean useAutofetchManager; - private final boolean hardRefs; private final Map nodePathMap = new HashMap(); @@ -66,10 +62,9 @@ public class DLoadContext implements LoadContext { boolean excludeBeanCache, ObjectGraphNode parentNode, boolean useAutofetchManager) { this.ebeanServer = ebeanServer; - this.hardRefs = GlobalProperties.getBoolean("ebean.hardrefs", false); this.defaultBatchSize = ebeanServer.getLazyLoadBatchSize(); this.rootDescriptor = rootDescriptor; - this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, defaultBatchSize, null, createBeanLoadList()); + this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, defaultBatchSize, null); this.readOnly = readOnly; this.excludeBeanCache = excludeBeanCache; this.useAutofetchManager = useAutofetchManager; @@ -210,6 +205,14 @@ public class DLoadContext implements LoadContext { public String getRelativePath() { return relativePath; } + + protected String getFullPath(String path) { + if (relativePath == null) { + return path; + } else { + return relativePath + "." + path; + } + } protected SpiEbeanServer getEbeanServer() { return ebeanServer; @@ -282,24 +285,7 @@ public class DLoadContext implements LoadContext { BeanPropertyAssocMany p = (BeanPropertyAssocMany)getBeanProperty(rootDescriptor, path); - return new DLoadManyContext(this, p, path, batchSize, queryProps, createBeanCollectionLoadList()); - } - - - private DLoadList createBeanLoadList() { - if (hardRefs){ - return new DLoadHardList(); - } else { - return new DLoadWeakList(); - } - } - - private DLoadList> createBeanCollectionLoadList() { - if (hardRefs){ - return new DLoadHardList>(); - } else { - return new DLoadWeakList>(); - } + return new DLoadManyContext(this, p, path, batchSize, queryProps); } private DLoadBeanContext createBeanContext(String path, int batchSize, OrmQueryProperties queryProps) { @@ -307,11 +293,10 @@ public class DLoadContext implements LoadContext { BeanPropertyAssoc p = (BeanPropertyAssoc)getBeanProperty(rootDescriptor, path); BeanDescriptor targetDescriptor = p.getTargetDescriptor(); - return new DLoadBeanContext(this, targetDescriptor, path, batchSize, queryProps, createBeanLoadList()); + return new DLoadBeanContext(this, targetDescriptor, path, batchSize, queryProps); } private BeanProperty getBeanProperty(BeanDescriptor desc, String path){ - return desc.getBeanPropertyFromPath(path); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadHardList.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadHardList.java deleted file mode 100644 index 21177ffb2..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadHardList.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.avaje.ebeaninternal.server.loadcontext; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; - -public class DLoadHardList implements DLoadList { - - private static final Logger logger = LoggerFactory.getLogger(DLoadHardList.class); - - protected final ArrayList list = new ArrayList(); - - protected int removedFromTop; - - protected DLoadHardList() { - - } - - public int add(T e) { - synchronized (this) { - int i = list.size(); - list.add(e); - return i; - } - } - - public void removeEntry(int position) { - synchronized (this) { - T object = list.get(position); - if (object == null) { - logger.warn("removeEntry found no Object for position[" + position + "]"); - } else { - // just set the entry to null - list.set(position, null); - } - if (position == removedFromTop) { - removedFromTop++; - } - } - } - - public List getNextBatch(int batchSize) { - if (removedFromTop >= list.size()){ - return new ArrayList(0); - } - return getLoadBatch(removedFromTop, batchSize, true); - } - - public List getLoadBatch(int position, int batchSize) { - return getLoadBatch(position, batchSize, false); - } - - private List getLoadBatch(int position, int batchSize, boolean ignoreMissing) { - - synchronized (this) { - if (batchSize < 1) { - throw new RuntimeException("batchSize " + batchSize + " < 1 ??!!"); - } - - ArrayList batch = new ArrayList(); - - if (!addObjectToBatchAt(batch, position) && !ignoreMissing) { - String msg = "getLoadBatch position[" + position + "] didn't find a bean in the list?"; - throw new IllegalStateException(msg); - } - - for (int i = position; i < list.size(); i++) { - addObjectToBatchAt(batch, i); - if (batch.size() == batchSize) { - // found enough beans going forward - return batch; - } - } - - // search the front of the list to fill our batch - for (int i = removedFromTop; i < position; i++) { - addObjectToBatchAt(batch, i); - if (batch.size() == batchSize) { - // found enough beans going forward from start of list - return batch; - } - } - - return batch; - } - } - - private boolean addObjectToBatchAt(ArrayList batch, int i) { - - boolean found = false; - T object = list.get(i); - if (object != null) { - found = true; - batch.add(object); - // set it to null saying we have loaded this one - list.set(i, null); - } - - if (i == removedFromTop) { - removedFromTop++; - } - return found; - } - -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java index 1d20d7e4b..a69603bb4 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.loadcontext; +import java.util.ArrayList; import java.util.List; import com.avaje.ebean.bean.BeanCollection; @@ -8,55 +9,45 @@ import com.avaje.ebean.bean.ObjectGraphNode; import com.avaje.ebean.bean.PersistenceContext; import com.avaje.ebeaninternal.api.LoadManyContext; import com.avaje.ebeaninternal.api.LoadManyRequest; +import com.avaje.ebeaninternal.api.LoadManyBuffer; import com.avaje.ebeaninternal.api.SpiQuery; import com.avaje.ebeaninternal.server.core.OrmQueryRequest; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany; import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; -public class DLoadManyContext implements LoadManyContext, BeanCollectionLoader { - - protected final DLoadContext parent; - - protected final String fullPath; - - private final BeanDescriptor desc; +public class DLoadManyContext extends DLoadBaseContext implements LoadManyContext { - private final BeanPropertyAssocMany property; - - private final String path; + protected final BeanPropertyAssocMany property; + + private List bufferList; + + private LoadBuffer currentBuffer; + + public DLoadManyContext(DLoadContext parent, BeanPropertyAssocMany property, + String path, int defaultBatchSize, OrmQueryProperties queryProps) { - private final int batchSize; - - private final OrmQueryProperties queryProps; - - private final DLoadList> weakList; - - public DLoadManyContext(DLoadContext parent, BeanPropertyAssocMany p, - String path, int batchSize, OrmQueryProperties queryProps, DLoadList> weakList) { - - this.parent = parent; - this.property = p; - this.desc = p.getBeanDescriptor(); - this.path = path; - this.batchSize = batchSize; - this.queryProps = queryProps; - this.weakList = weakList;//new DLoadWeakList>(); - - if (parent.getRelativePath() == null){ - this.fullPath = path; - } else { - this.fullPath = parent.getRelativePath()+"."+path; - } + super(parent, property.getBeanDescriptor(), path, defaultBatchSize, queryProps); + this.property = property; + this.currentBuffer = createBuffer(firstBatchSize); + this.bufferList = queryFetch ? new ArrayList() : null; } - public void configureQuery(SpiQuery query){ + private LoadBuffer createBuffer(int size) { + LoadBuffer buffer = new LoadBuffer(this, size); + if (bufferList != null) { + bufferList.add(buffer); + } + return buffer; + } + + public void configureQuery(SpiQuery query){ // propagate the readOnly state - if (parent.isReadOnly() != null){ - query.setReadOnly(parent.isReadOnly()); - } + if (parent.isReadOnly() != null){ + query.setReadOnly(parent.isReadOnly()); + } query.setParentNode(getObjectGraphNode()); if (queryProps != null){ @@ -82,18 +73,6 @@ public class DLoadManyContext implements LoadManyContext, BeanCollectionLoader { } } - public String getFullPath() { - return fullPath; - } - - public PersistenceContext getPersistenceContext() { - return parent.getPersistenceContext(); - } - - public int getBatchSize() { - return batchSize; - } - public BeanPropertyAssocMany getBeanProperty() { return property; } @@ -102,60 +81,130 @@ public class DLoadManyContext implements LoadManyContext, BeanCollectionLoader { return desc; } - public String getPath() { - return path; - } public String getName() { return parent.getEbeanServer().getName(); } public void register(BeanCollection bc){ - int pos = weakList.add(bc); - bc.setLoader(pos, this); + + bc.setLoader(0, currentBuffer); + if (currentBuffer.add(bc)) { + // the currentBuffer is full so create another one + currentBuffer = createBuffer(secondaryBatchSize); + } } - public void loadMany(BeanCollection bc, boolean onlyIds) { - - int position = bc.getLoaderIndex(); - - LoadManyRequest req; - synchronized (weakList) { - boolean hitCache = desc.isBeanCaching() && !onlyIds && !parent.isExcludeBeanCache(); - if (hitCache){ - Object ownerBean = bc.getOwnerBean(); - BeanDescriptor parentDesc = desc.getBeanDescriptor(ownerBean.getClass()); - Object parentId = parentDesc.getId(ownerBean); - if (parentDesc.cacheLoadMany(property, bc, parentId, parent.isReadOnly())) { - // we loaded the bean from cache - weakList.removeEntry(position); - return; - } - } - - List> loadBatch = weakList.getLoadBatch(position, batchSize); - req = new LoadManyRequest(this, loadBatch, null, batchSize, true, onlyIds, hitCache); - } - parent.getEbeanServer().loadMany(req); - } public void loadSecondaryQuery(OrmQueryRequest parentRequest, int requestedBatchSize, boolean all){ - do { - LoadManyRequest req; - synchronized (weakList) { - List> batch = weakList.getNextBatch(requestedBatchSize); - if (batch.size() == 0){ - return; - } - req = new LoadManyRequest(this, batch, parentRequest.getTransaction(), requestedBatchSize, false, false, false); - } - parent.getEbeanServer().loadMany(req); - if (!all){ - // queryFirst(batch) - break; - } - } while (true); + if (!queryFetch) { + throw new IllegalStateException("Not expecting loadSecondaryQuery() to be called?"); + } + synchronized (this) { + if (bufferList != null) { + for (LoadBuffer loadBuffer : bufferList) { + if (!loadBuffer.list.isEmpty()) { + LoadManyRequest req = new LoadManyRequest(loadBuffer, parentRequest.getTransaction(), requestedBatchSize, false, false, false); + parent.getEbeanServer().loadMany(req); + if (!queryProps.isQueryFetchAll()) { + // Stop - only fetch the first batch ... the rest will be lazy loaded + break; + } + } + } + + // this is only run once - secondary query is a one shot deal + this.bufferList = null; + } + } } + /** + * A buffer for batch loading bean collections on a given path. + * Supports batch lazy loading and secondary query loading. + */ + public static class LoadBuffer implements BeanCollectionLoader, LoadManyBuffer { + + private final DLoadManyContext context; + private final int batchSize; + private final List> list; + + public LoadBuffer(DLoadManyContext context, int batchSize) { + this.context = context; + this.batchSize = batchSize; + this.list = new ArrayList>(batchSize); + } + + /** + * Return true if the buffer is full. + */ + public boolean add(BeanCollection bc) { + list.add(bc); + return batchSize == list.size(); + } + + @Override + public List> getBatch() { + return list; + } + + @Override + public BeanPropertyAssocMany getBeanProperty() { + return context.property; + } + + @Override + public ObjectGraphNode getObjectGraphNode() { + return context.getObjectGraphNode(); + } + + @Override + public void configureQuery(SpiQuery query){ + context.configureQuery(query); + } + + @Override + public String getName() { + return context.serverName; + } + + @Override + public BeanDescriptor getBeanDescriptor() { + return context.desc; + } + + @Override + public PersistenceContext getPersistenceContext() { + return context.getPersistenceContext(); + } + + @Override + public String getFullPath() { + return context.fullPath; + } + + public void loadMany(BeanCollection bc, boolean onlyIds) { + + synchronized (this) { + boolean useCache = context.hitCache && !onlyIds; + if (useCache) { + Object ownerBean = bc.getOwnerBean(); + BeanDescriptor parentDesc = context.desc.getBeanDescriptor(ownerBean.getClass()); + Object parentId = parentDesc.getId(ownerBean); + if (parentDesc.cacheLoadMany(context.property, bc, parentId, context.parent.isReadOnly())) { + // we loaded the bean from cache + list.remove(bc); + return; + } + } + + // Should reduce the list by checking each beanCollection in the L2 first before executing the query + + LoadManyRequest req = new LoadManyRequest(this, null, batchSize, true, onlyIds, useCache); + context.parent.getEbeanServer().loadMany(req); + } + } + + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadWeakList.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadWeakList.java deleted file mode 100644 index 2dadc4283..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadWeakList.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.avaje.ebeaninternal.server.loadcontext; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; - -public class DLoadWeakList implements DLoadList { - - private static final Logger logger = LoggerFactory.getLogger(DLoadWeakList.class); - - protected final ArrayList> list = new ArrayList>(); - - protected int removedFromTop; - - protected DLoadWeakList() { - - } - - public int add(T e) { - synchronized (this) { - int i = list.size(); - list.add(new WeakReference(e)); - return i; - } - } - - public void removeEntry(int position) { - synchronized (this) { - WeakReference wref = list.get(position); - if (wref == null) { - logger.warn("removeEntry found no WeakReference for position[" + position + "]"); - } else { - // just set the entry to null - list.set(position, null); - T object = wref.get(); - if (object == null) { - logger.warn("removeEntry found no Object held by WeakReference for position[" + position + "]"); - } - } - if (position == removedFromTop) { - removedFromTop++; - } - } - } - - public List getNextBatch(int batchSize) { - if (removedFromTop >= list.size()){ - return new ArrayList(0); - } - return getLoadBatch(removedFromTop, batchSize, true); - } - - public List getLoadBatch(int position, int batchSize) { - return getLoadBatch(position, batchSize, false); - } - - private List getLoadBatch(int position, int batchSize, boolean ignoreMissing) { - - synchronized (this) { - if (batchSize < 1) { - throw new RuntimeException("batchSize " + batchSize + " < 1 ??!!"); - } - - ArrayList batch = new ArrayList(); - - if (!addObjectToBatchAt(batch, position) && !ignoreMissing) { - String msg = "getLoadBatch position[" + position + "] didn't find a bean in the list?"; - throw new IllegalStateException(msg); - } - - for (int i = position; i < list.size(); i++) { - addObjectToBatchAt(batch, i); - if (batch.size() == batchSize) { - // found enough beans going forward - return batch; - } - } - - // search the front of the list to fill our batch - for (int i = removedFromTop; i < position; i++) { - addObjectToBatchAt(batch, i); - if (batch.size() == batchSize) { - // found enough beans going forward from start of list - return batch; - } - } - - return batch; - } - } - - private boolean addObjectToBatchAt(ArrayList batch, int i) { - - boolean found = false; - WeakReference wref = list.get(i); - if (wref != null) { - T object = wref.get(); - if (object == null) { - logger.warn("Bean is null from weak reference"); - } else { - found = true; - batch.add(object); - } - // set it to null saying we have loaded this one - list.set(i, null); - } - if (i == removedFromTop) { - removedFromTop++; - } - return found; - } - -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java index a16d10741..f813629e0 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java @@ -12,7 +12,6 @@ import java.util.Set; import com.avaje.ebean.ExpressionFactory; import com.avaje.ebean.FetchConfig; import com.avaje.ebean.OrderBy; -import com.avaje.ebean.OrderBy.Property; import com.avaje.ebean.Query; import com.avaje.ebean.event.BeanQueryRequest; import com.avaje.ebeaninternal.api.SpiExpressionFactory; @@ -187,8 +186,7 @@ public class OrmQueryProperties implements Serializable { if (filterMany == null){ return null; } - filterMany.trimPath(trimPath); - return filterMany; + return filterMany.trimPath(trimPath); } /** @@ -248,11 +246,7 @@ public class OrmQueryProperties implements Serializable { } if (orderBy != null){ - List orderByProps = orderBy.getProperties(); - for (int i = 0; i < orderByProps.size(); i++) { - orderByProps.get(i).trim(path); - } - query.setOrder(orderBy); + query.setOrder(orderBy.copyWithTrim(path)); } } diff --git a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java index 6f8eea7d3..8c18dd40a 100644 --- a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java @@ -33,13 +33,13 @@ public class DefaultExpressionList implements SpiExpressionList { private static final long serialVersionUID = -6992345500247035947L; - private final ArrayList list = new ArrayList(); + protected final ArrayList list = new ArrayList(); - private final Query query; + protected final Query query; - private final ExpressionList parentExprList; + protected final ExpressionList parentExprList; - private transient ExpressionFactory expr; + protected transient ExpressionFactory expr; private final String exprLang; private final String listAndStart; @@ -69,7 +69,7 @@ public class DefaultExpressionList implements SpiExpressionList { } } - public void trimPath(int prefixTrim) { + public SpiExpressionList trimPath(int prefixTrim) { throw new RuntimeException("Only allowed on FilterExpressionList"); } diff --git a/src/main/java/com/avaje/ebeaninternal/util/FilterExpressionList.java b/src/main/java/com/avaje/ebeaninternal/util/FilterExpressionList.java index b14513660..28d08981a 100644 --- a/src/main/java/com/avaje/ebeaninternal/util/FilterExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/util/FilterExpressionList.java @@ -15,6 +15,7 @@ import com.avaje.ebean.OrderBy; import com.avaje.ebean.PagingList; import com.avaje.ebean.Query; import com.avaje.ebean.QueryListener; +import com.avaje.ebeaninternal.api.SpiExpressionList; import com.avaje.ebeaninternal.server.expression.FilterExprPath; public class FilterExpressionList extends DefaultExpressionList { @@ -31,8 +32,8 @@ public class FilterExpressionList extends DefaultExpressionList { this.rootQuery = rootQuery; } - public void trimPath(int prefixTrim) { - pathPrefix.trimPath(prefixTrim); + public SpiExpressionList trimPath(int prefixTrim) { + return new FilterExpressionList(pathPrefix.trimPath(prefixTrim), expr, rootQuery); } public FilterExprPath getPathPrefix() { diff --git a/src/test/java/com/avaje/ebeaninternal/server/loadcontext/TestDLoadWeakList.java b/src/test/java/com/avaje/ebeaninternal/server/loadcontext/TestDLoadWeakList.java deleted file mode 100644 index 01a749bb5..000000000 --- a/src/test/java/com/avaje/ebeaninternal/server/loadcontext/TestDLoadWeakList.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.avaje.ebeaninternal.server.loadcontext; - -import java.util.List; - -import org.junit.Assert; -import org.junit.Test; - -import com.avaje.ebean.BaseTestCase; - -public class TestDLoadWeakList extends BaseTestCase { - - @Test - public void test() { - - String s0 = new String("zero"); - String s1 = new String("one"); - String s2 = new String("two"); - String s3 = new String("three"); - String s4 = new String("four"); - String s5 = new String("five"); - String s6 = new String("six"); - String s7 = new String("seven"); - String s8 = new String("eight"); - String s9 = new String("nine"); - String s10 = new String("ten"); - - DLoadWeakList list = new DLoadWeakList(); - - list.add(s0); - list.add(s1); - list.add(s2); - list.add(s3); - list.add(s4); - list.add(s5); - list.add(s6); - list.add(s7); - list.add(s8); - list.add(s9); - list.add(s10); - - Assert.assertEquals(11, list.list.size()); - - System.gc(); - - try { - Thread.sleep(300); - } catch (InterruptedException e) { - e.printStackTrace(); - } - // this is really only a HINT, so no guarantee - // .. but the SUN JVM does do the business - System.gc(); - - Assert.assertEquals(11, list.list.size()); - - List b0 = list.getLoadBatch(0, 2); - Assert.assertEquals(2, b0.size()); - Assert.assertEquals("zero", b0.get(0)); - Assert.assertEquals("one", b0.get(1)); - - try { - b0 = list.getLoadBatch(0, 2); - Assert.assertTrue(false); - } catch (IllegalStateException e) { - Assert.assertTrue(true); - } - b0 = list.getNextBatch(2); - Assert.assertEquals(2, b0.size()); - Assert.assertEquals("two", b0.get(0)); - Assert.assertEquals("three", b0.get(1)); - - list.removeEntry(1); - - b0 = list.getLoadBatch(7, 2); - Assert.assertEquals(2, b0.size()); - Assert.assertEquals("seven", b0.get(0)); - Assert.assertEquals("eight", b0.get(1)); - - } -} diff --git a/src/test/java/com/avaje/ebeaninternal/server/loadcontext/TestDLoadWeakListWithGC.java b/src/test/java/com/avaje/ebeaninternal/server/loadcontext/TestDLoadWeakListWithGC.java deleted file mode 100644 index 59df38066..000000000 --- a/src/test/java/com/avaje/ebeaninternal/server/loadcontext/TestDLoadWeakListWithGC.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.avaje.ebeaninternal.server.loadcontext; - -import java.lang.ref.WeakReference; -import java.util.List; - -import org.junit.Assert; -import org.junit.Test; - -import com.avaje.ebean.BaseTestCase; - -public class TestDLoadWeakListWithGC extends BaseTestCase { - - private void doStuffInMethodScope(DLoadWeakList list) { - String s5 = new String("five"); - String s6 = new String("six"); - String s7 = new String("seven"); - String s8 = new String("eight"); - String s9 = new String("nine"); - String s10 = new String("ten"); - - list.add(s5); - list.add(s6); - list.add(s7); - list.add(s8); - list.add(s9); - list.add(s10); - } - - @Test - public void test() { - - String s0 = new String("zero"); - String s1 = new String("one"); - String s2 = new String("two"); - String s3 = new String("three"); - String s4 = new String("four"); - - DLoadWeakList list = new DLoadWeakList(); - - list.add(s0); - list.add(s1); - list.add(s2); - list.add(s3); - list.add(s4); - - int initialSize = list.list.size(); - - doStuffInMethodScope(list); - Assert.assertEquals(11, list.list.size()); - - System.gc(); - - try { - Thread.sleep(300); - } catch (InterruptedException e) { - e.printStackTrace(); - } - // this is really only a HINT, so no guarantee - // .. but the SUN JVM does do the business - System.gc(); - - Assert.assertEquals(11, list.list.size()); - - // these weak refs are still good as we still - // have a hard reference to the objects in scope - for (int i = 0; i < initialSize; i++) { - WeakReference weakReference = list.list.get(i); - Assert.assertNotNull(weakReference); - Assert.assertNotNull(weakReference.get()); - } - - // these weak refs are all null as we don't have - // hard refs to the objects in scope ... and it just - // so happens the System.gc() in sun jvm is aggressive here. - for (int i = initialSize; i < list.list.size(); i++) { - WeakReference weakReference = list.list.get(i); - Assert.assertNotNull(weakReference); - Assert.assertNull(weakReference.get()); - } - - List b0 = list.getLoadBatch(0, 2); - Assert.assertEquals(2, b0.size()); - Assert.assertEquals("zero", b0.get(0)); - Assert.assertEquals("one", b0.get(1)); - - try { - b0 = list.getLoadBatch(0, 2); - Assert.assertTrue(false); - } catch (IllegalStateException e) { - Assert.assertTrue(true); - } - b0 = list.getNextBatch(2); - Assert.assertEquals(2, b0.size()); - Assert.assertEquals("two", b0.get(0)); - Assert.assertEquals("three", b0.get(1)); - - list.removeEntry(1); - - try { - // past the gc blown area - b0 = list.getLoadBatch(7, 2); - Assert.assertTrue(false); - } catch (IllegalStateException e) { - Assert.assertTrue(true); - } - - } -} diff --git a/src/test/java/com/avaje/tests/query/TestQueryFetchJoinWithOrder.java b/src/test/java/com/avaje/tests/query/TestQueryFetchJoinWithOrder.java index a74bfa83c..34fba6cc3 100644 --- a/src/test/java/com/avaje/tests/query/TestQueryFetchJoinWithOrder.java +++ b/src/test/java/com/avaje/tests/query/TestQueryFetchJoinWithOrder.java @@ -19,19 +19,27 @@ public class TestQueryFetchJoinWithOrder extends BaseTestCase { ResetBasicData.reset(); - List list = Ebean.find(Order.class).fetch("details", new FetchConfig().query()).order() - .asc("id").order().desc("details.id").findList(); + List list = Ebean.find(Order.class) + .fetch("details", new FetchConfig().query()) + .order().asc("id") + .order().desc("details.id").findList(); Assert.assertNotNull(list); - List list2 = Ebean.find(Order.class).fetch("customer", new FetchConfig().query()) - .fetch("customer.contacts").order().asc("id").order().asc("customer.contacts.lastName") + List list2 = Ebean.find(Order.class) + .fetch("customer", new FetchConfig().query(5)) + .fetch("customer.contacts") + .order().asc("id") + .order().asc("customer.contacts.lastName") .findList(); Assert.assertNotNull(list2); - List list3 = Ebean.find(Customer.class).fetch("orders").filterMany("orders") - .eq("status", Order.Status.NEW).order().desc("orders.id").findList(); + List list3 = Ebean.find(Customer.class) + .fetch("orders") + .filterMany("orders").eq("status", Order.Status.NEW) + .order().desc("orders.id") + .findList(); Assert.assertNotNull(list3); diff --git a/src/test/java/com/avaje/tests/query/TestQueryJoinBatchSize.java b/src/test/java/com/avaje/tests/query/TestQueryJoinBatchSize.java new file mode 100644 index 000000000..56f9158b8 --- /dev/null +++ b/src/test/java/com/avaje/tests/query/TestQueryJoinBatchSize.java @@ -0,0 +1,47 @@ +package com.avaje.tests.query; + +import java.util.List; + +import org.junit.Test; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.Ebean; +import com.avaje.ebean.FetchConfig; +import com.avaje.tests.model.basic.Customer; +import com.avaje.tests.model.basic.Order; +import com.avaje.tests.model.basic.ResetBasicData; + +public class TestQueryJoinBatchSize extends BaseTestCase { + + @Test + public void test() { + + ResetBasicData.reset(); + + List list = Ebean.find(Order.class) + .fetch("customer", new FetchConfig().queryFirst(3).lazy(2)) + //.fetch("orders.details", new FetchConfig().query()) + //.fetch("orders.shipments", new FetchConfig().query()) + .findList(); + + for (Order order : list) { + Customer customer = order.getCustomer(); + customer.getName(); + } + + } + +// @Test +// public void test() { +// +// ResetBasicData.reset(); +// +// Ebean.find(Customer.class) +// .fetch("orders", new FetchConfig().query(3).lazy(2)) +// //.fetch("orders.details", new FetchConfig().query()) +// //.fetch("orders.shipments", new FetchConfig().query()) +// .findList(); +// +// } + +} diff --git a/src/test/java/com/avaje/tests/query/TestQueryJoinQueryNonRoot.java b/src/test/java/com/avaje/tests/query/TestQueryJoinQueryNonRoot.java index 9fffbefc2..5aa66e77d 100644 --- a/src/test/java/com/avaje/tests/query/TestQueryJoinQueryNonRoot.java +++ b/src/test/java/com/avaje/tests/query/TestQueryJoinQueryNonRoot.java @@ -30,7 +30,8 @@ public class TestQueryJoinQueryNonRoot extends BaseTestCase { List list = Ebean.find(Order.class).fetch("customer") .fetch("customer.contacts", "firstName", new FetchConfig().query().lazy(10)) - .fetch("customer.contacts.group").where().lt("id", 3).findList(); + .fetch("customer.contacts.group") + .where().lt("id", 3).findList(); Assert.assertNotNull(list); Assert.assertTrue(list.size() > 0); diff --git a/src/test/java/com/avaje/tests/rawsql/TestRawSqlOrmWrapper3.java b/src/test/java/com/avaje/tests/rawsql/TestRawSqlOrmWrapper3.java index fead81bfa..5fddb439c 100644 --- a/src/test/java/com/avaje/tests/rawsql/TestRawSqlOrmWrapper3.java +++ b/src/test/java/com/avaje/tests/rawsql/TestRawSqlOrmWrapper3.java @@ -36,8 +36,11 @@ public class TestRawSqlOrmWrapper3 extends BaseTestCase { List list2 = Ebean.find(OrderAggregate.class).setRawSql(rawSql) .fetch("order", new FetchConfig().query()) - .fetch("order.details", new FetchConfig().query()).where().gt("order.id", 2).having() - .gt("totalAmount", 10).filterMany("order.details").gt("unitPrice", 2d).findList(); + .fetch("order.details", new FetchConfig().query()) + .where().gt("order.id", 2) + .having().gt("totalAmount", 10) + .filterMany("order.details").gt("unitPrice", 2d) + .findList(); output(list2);