#360 ENH: Add query setDisableLazyLoading(true) ... for use when graph serialised via external library (and you don't want lazy loading to fire)

This commit is contained in:
Robin Bygrave
2015-07-29 20:31:25 +12:00
parent 59d009bc94
commit 3d4bd15dac
14 changed files with 239 additions and 39 deletions
+7
View File
@@ -1257,4 +1257,11 @@ public interface Query<T> extends Serializable {
*/
Class<T> getBeanType();
/**
* Set true if you want to disable lazy loading.
* <p>
* That is, once the object graph is returned further lazy loading is disabled.
* </p>
*/
Query<T> setDisableLazyLoading(boolean disableLazyLoading);
}
@@ -576,9 +576,14 @@ public interface SpiQuery<T> extends Query<T> {
*/
int getFirstRow();
/**
* Return true if lazy loading has been disabled on the query.
*/
boolean isDisableLazyLoading();
/**
* Internally set by Ebean when this query must use the DISTINCT keyword.
* <p/>
* <p>
* This does not exclude/remove the use of the id property.
*/
Query<T> setSqlDistinct(boolean sqlDistinct);
@@ -152,7 +152,7 @@ public class DRawSqlSelect {
}
}
SqlTreeNode sqlRoot = new SqlTreeNodeRoot(desc, selectProps, null, withId);
SqlTreeNode sqlRoot = new SqlTreeNodeRoot(desc, selectProps, withId, false);
return new SqlTree(desc.getName(), sqlRoot);
}
@@ -49,6 +49,8 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
if (parent.isReadOnly() != null) {
query.setReadOnly(parent.isReadOnly());
}
// propagate the asOf and lazy loading mode
query.setDisableLazyLoading(parent.isDisableLazyLoading());
query.asOf(parent.getAsOf());
query.setParentNode(objectGraphNode);
query.setLazyLoadProperty(lazyLoadProperty);
@@ -36,8 +36,9 @@ public class DLoadContext implements LoadContext {
private final Boolean readOnly;
private final boolean excludeBeanCache;
private final int defaultBatchSize;
/**
private final boolean disableLazyLoading;
/**
* The path relative to the root of the object graph.
*/
private final String relativePath;
@@ -59,7 +60,8 @@ public class DLoadContext implements LoadContext {
SpiQuery<?> query = request.getQuery();
this.asOf = query.getAsOf();
this.readOnly = query.isReadOnly();
this.readOnly = query.isReadOnly();
this.disableLazyLoading = query.isDisableLazyLoading();
this.excludeBeanCache = Boolean.FALSE.equals(query.isUseBeanCache());
this.useAutofetchManager = query.getAutoFetchManager() != null;
@@ -219,6 +221,10 @@ public class DLoadContext implements LoadContext {
return asOf;
}
protected boolean isDisableLazyLoading() {
return disableLazyLoading;
}
public PersistenceContext getPersistenceContext() {
return persistenceContext;
}
@@ -61,6 +61,8 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
query.setReadOnly(parent.isReadOnly());
}
// propagate the asOf and lazy loading mode
query.setDisableLazyLoading(parent.isDisableLazyLoading());
query.asOf(parent.getAsOf());
query.setParentNode(objectGraphNode);
@@ -71,6 +71,8 @@ public class SqlTreeBuilder {
*/
private final boolean rawNoId;
private final boolean disableLazyLoad;
private SqlTreeNode rootNode;
/**
@@ -81,6 +83,7 @@ public class SqlTreeBuilder {
this.rawSql = true;
this.desc = request.getBeanDescriptor();
this.rawNoId = rawNoId;
this.disableLazyLoad = request.getQuery().isDisableLazyLoading();
this.query = null;
this.subQuery = false;
this.queryDetail = queryDetail;
@@ -104,6 +107,7 @@ public class SqlTreeBuilder {
this.rawNoId = false;
this.desc = request.getBeanDescriptor();
this.query = request.getQuery();
this.disableLazyLoad = (query == null) ? false : query.isDisableLazyLoading();
this.subQuery = Type.SUBQUERY.equals(query.getType()) || Type.ID_LIST.equals(query.getType());
this.includeJoin = query.getIncludeTableJoin();
@@ -252,8 +256,7 @@ public class SqlTreeBuilder {
}
}
private SqlTreeNode buildNode(String prefix, BeanPropertyAssoc<?> prop, BeanDescriptor<?> desc,
List<SqlTreeNode> myList) {
private SqlTreeNode buildNode(String prefix, BeanPropertyAssoc<?> prop, BeanDescriptor<?> desc, List<SqlTreeNode> myList) {
OrmQueryProperties queryProps = queryDetail.getChunk(prefix, false);
@@ -265,14 +268,13 @@ public class SqlTreeBuilder {
// Optional many property for lazy loading query
BeanPropertyAssocMany<?> lazyLoadMany = (query == null) ? null : query.getLazyLoadForParentsProperty();
boolean withId = !rawNoId && !subQuery && (query == null || !query.isDistinct());
return new SqlTreeNodeRoot(desc, props, myList, withId, includeJoin, lazyLoadMany, SpiQuery.TemporalMode.of(query));
return new SqlTreeNodeRoot(desc, props, myList, withId, includeJoin, lazyLoadMany, SpiQuery.TemporalMode.of(query), disableLazyLoad);
} else if (prop instanceof BeanPropertyAssocMany<?>) {
return new SqlTreeNodeManyRoot(prefix, (BeanPropertyAssocMany<?>) prop, props, myList);
return new SqlTreeNodeManyRoot(prefix, (BeanPropertyAssocMany<?>) prop, props, myList, disableLazyLoad);
} else {
return new SqlTreeNodeBean(prefix, prop, props, myList, true);
return new SqlTreeNodeBean(prefix, prop, props, myList, true, disableLazyLoad);
}
}
@@ -87,49 +87,58 @@ public class SqlTreeNodeBean implements SqlTreeNode {
*/
protected String intersectionAsOfTableAlias;
public SqlTreeNodeBean(String prefix, BeanPropertyAssoc<?> beanProp, SqlTreeProperties props,
List<SqlTreeNode> myChildren, boolean withId) {
/**
* Construct for Raw SQL.
*/
public SqlTreeNodeBean(BeanDescriptor<?> desc, SqlTreeProperties props, boolean withId, boolean disableLazyLoad) {
this(null, null, desc, props, null, withId, null, null, disableLazyLoad);
}
this(prefix, beanProp, beanProp.getTargetDescriptor(), props, myChildren, withId, null, SpiQuery.TemporalMode.CURRENT);
/**
* Construct for leaf node.
*/
public SqlTreeNodeBean(String prefix, BeanPropertyAssoc<?> beanProp, SqlTreeProperties props,
List<SqlTreeNode> myChildren, boolean withId, boolean disableLazyLoad) {
this(prefix, beanProp, beanProp.getTargetDescriptor(), props, myChildren, withId, null, SpiQuery.TemporalMode.CURRENT, disableLazyLoad);
}
/**
* Construct for root node.
*/
public SqlTreeNodeBean(BeanDescriptor<?> desc, SqlTreeProperties props, List<SqlTreeNode> myList, boolean withId,
BeanPropertyAssocMany<?> many, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) {
this(null, null, desc, props, myList, withId, many, temporalMode, disableLazyLoad);
}
/**
* Create with the appropriate node.
*/
public SqlTreeNodeBean(String prefix, BeanPropertyAssoc<?> beanProp, BeanDescriptor<?> desc,
SqlTreeProperties props, List<SqlTreeNode> myChildren, boolean withId, BeanPropertyAssocMany<?> lazyLoadParent, SpiQuery.TemporalMode temporalMode) {
private SqlTreeNodeBean(String prefix, BeanPropertyAssoc<?> beanProp, BeanDescriptor<?> desc, SqlTreeProperties props,
List<SqlTreeNode> myChildren, boolean withId, BeanPropertyAssocMany<?> lazyLoadParent,
SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) {
this.lazyLoadParent = lazyLoadParent;
this.lazyLoadParentIdBinder = (lazyLoadParent == null) ? null : lazyLoadParent.getBeanDescriptor().getIdBinder();
this.prefix = prefix;
this.nodeBeanProp = beanProp;
this.desc = desc;
this.inheritInfo = desc.getInheritInfo();
this.idBinder = desc.getIdBinder();
this.temporalMode = temporalMode;
this.temporalVersions = temporalMode == SpiQuery.TemporalMode.VERSIONS;
this.inheritInfo = desc.getInheritInfo();
this.nodeBeanProp = beanProp;
this.extraWhere = (beanProp == null) ? null : beanProp.getExtraWhere();
this.idBinder = desc.getIdBinder();
// the bean has an Id property and we want to use it
this.readId = withId && (desc.getIdProperty() != null);
this.disableLazyLoad = !readId || desc.isSqlSelectBased() || temporalVersions;
this.disableLazyLoad = disableLazyLoad || !readId || desc.isSqlSelectBased() || temporalVersions;
this.tableJoins = props.getTableJoins();
this.partialObject = props.isPartialObject();
this.readOnlyLeaf = props.isReadOnly();
this.properties = props.getProps();
if (myChildren == null) {
children = NO_CHILDREN;
} else {
children = myChildren.toArray(new SqlTreeNode[myChildren.size()]);
}
this.children = myChildren == null ? NO_CHILDREN : myChildren.toArray(new SqlTreeNode[myChildren.size()]);
pathMap = createPathMap(prefix, desc);
}
@@ -10,10 +10,10 @@ import com.avaje.ebeaninternal.server.deploy.DbSqlContext;
public final class SqlTreeNodeManyRoot extends SqlTreeNodeBean {
final BeanPropertyAssocMany<?> manyProp;
private final BeanPropertyAssocMany<?> manyProp;
public SqlTreeNodeManyRoot(String prefix, BeanPropertyAssocMany<?> prop, SqlTreeProperties props, List<SqlTreeNode> myList) {
super(prefix, prop, prop.getTargetDescriptor(), props, myList, true, null, null);
public SqlTreeNodeManyRoot(String prefix, BeanPropertyAssocMany<?> prop, SqlTreeProperties props, List<SqlTreeNode> myList, boolean disableLazyLoad) {
super(prefix, prop, props, myList, true, disableLazyLoad);
this.manyProp = prop;
}
@@ -19,13 +19,17 @@ public final class SqlTreeNodeRoot extends SqlTreeNodeBean {
* Specify for SqlSelect to include an Id property or not.
*/
public SqlTreeNodeRoot(BeanDescriptor<?> desc, SqlTreeProperties props, List<SqlTreeNode> myList, boolean withId,
TableJoin includeJoin, BeanPropertyAssocMany<?> many, SpiQuery.TemporalMode temporalMode) {
super(null, null, desc, props, myList, withId, many, temporalMode);
TableJoin includeJoin, BeanPropertyAssocMany<?> many, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) {
super(desc, props, myList, withId, many, temporalMode, disableLazyLoad);
this.includeJoin = includeJoin;
}
public SqlTreeNodeRoot(BeanDescriptor<?> desc, SqlTreeProperties props, List<SqlTreeNode> myList, boolean withId) {
super(null, null, desc, props, myList, withId, null, null);
/**
* Construct for raw sql.
*/
public SqlTreeNodeRoot(BeanDescriptor<?> desc, SqlTreeProperties props, boolean withId, boolean disableLazyLoad) {
super(desc, props, withId, disableLazyLoad);
this.includeJoin = null;
}
@@ -83,6 +83,11 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
private int firstRow;
/**
* Set to true to disable lazy loading on the object graph returned.
*/
private boolean disableLazyLoading;
/**
* Lazy loading batch size (can override server wide default).
*/
@@ -710,6 +715,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
builder.add(firstRow).add(maxRows).add(orderBy).add(forUpdate);
builder.add(rawWhereClause).add(additionalWhere).add(additionalHaving);
builder.add(mapKey);
builder.add(disableLazyLoading);
builder.add(id != null);
builder.add(asOf != null);
builder.add(rawSql == null ? 0 : rawSql.queryHash());
@@ -1138,6 +1144,16 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
this.includeTableJoin = includeTableJoin;
}
@Override
public Query<T> setDisableLazyLoading(boolean disableLazyLoading) {
this.disableLazyLoading = disableLazyLoading;
return this;
}
public boolean isDisableLazyLoading() {
return disableLazyLoading;
}
public int getFirstRow() {
return firstRow;
}
@@ -341,6 +341,9 @@ public class OrmQueryProperties implements Serializable {
if (filterMany != null) {
filterMany.queryPlanHash(request, builder);
}
builder.add(lazyFetchBatch);
builder.add(queryFetchBatch);
builder.add(queryFetchAll);
}
public String getProperties() {
@@ -0,0 +1,40 @@
package com.avaje.ebeaninternal.server.querydefn;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.FetchConfig;
import com.avaje.ebeaninternal.api.HashQueryPlan;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.tests.model.basic.Order;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class DefaultOrmQueryTest {
@Test
public void testQueryAutofetchHash() throws Exception {
SpiQuery<?> query1 = (SpiQuery<?>)Ebean.find(Order.class)
.select("status, shipDate")
.fetch("details", "orderQty, unitPrice", new FetchConfig().query())
.fetch("details.product", "sku, name");
HashQueryPlanBuilder b1 = new HashQueryPlanBuilder();
query1.queryAutofetchHash(b1);
HashQueryPlan hash1 = b1.build();
SpiQuery<?> query2 = (SpiQuery<?>)Ebean.find(Order.class)
.select("status, shipDate")
.fetch("details", "orderQty, unitPrice")
.fetch("details.product", "sku, name");
HashQueryPlanBuilder b2 = new HashQueryPlanBuilder();
query2.queryAutofetchHash(b2);
HashQueryPlan hash2 = b2.build();
assertThat(hash1).isNotEqualTo(hash2);
}
}
@@ -4,6 +4,7 @@ import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.FetchConfig;
import com.avaje.tests.model.basic.Order;
import com.avaje.tests.model.basic.OrderDetail;
import com.avaje.tests.model.basic.ResetBasicData;
import org.avaje.ebeantest.LoggedSqlCollector;
import org.junit.Test;
@@ -15,7 +16,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class TestQueryJoinToAssocOne extends BaseTestCase {
@Test
public void testLazyOnNonLoaded() {
public void testQueryJoinOnFullyPopulatedParent() {
ResetBasicData.reset();
@@ -23,6 +24,33 @@ public class TestQueryJoinToAssocOne extends BaseTestCase {
// This will use 2 SQL queries to build this object graph
List<Order> l0 = Ebean.find(Order.class)
.fetch("details", "orderQty, unitPrice", new FetchConfig().query())
.fetch("details.product", "sku, name")
.findList();
assertThat(l0).isNotEmpty();
List<String> loggedSql = LoggedSqlCollector.stop();
assertThat(loggedSql).hasSize(2);
String secondaryQuery = loggedSql.get(1);
assertThat(secondaryQuery).contains("select t0.order_id c0, t0.id c1,");
assertThat(secondaryQuery).contains(" from o_order_detail t0 left outer join o_product t1");
assertThat(secondaryQuery).contains(" (t0.order_id) in (?");
assertThat(secondaryQuery).contains(" order by t0.order_id, t0.id");
}
@Test
public void testQueryJoinOnPartiallyPopulatedParent() {
ResetBasicData.reset();
LoggedSqlCollector.start();
// This will use 2 SQL queries to build this object graph
List<Order> l0 = Ebean.find(Order.class)
.setUseCache(false)
.select("status, shipDate")
.fetch("details", "orderQty, unitPrice", new FetchConfig().query())
.fetch("details.product", "sku, name")
@@ -33,5 +61,81 @@ public class TestQueryJoinToAssocOne extends BaseTestCase {
List<String> loggedSql = LoggedSqlCollector.stop();
assertThat(loggedSql).hasSize(2);
String secondaryQuery = loggedSql.get(1);
assertThat(secondaryQuery).contains("select t0.order_id c0, t0.id c1,");
assertThat(secondaryQuery).contains(" from o_order_detail t0 left outer join o_product t1");
assertThat(secondaryQuery).contains(" (t0.order_id) in (?");
assertThat(secondaryQuery).contains(" order by t0.order_id, t0.id");
}
@Test
public void testQueryJoinOnPartiallyPopulatedParent_withLazyLoadingDisabled() {
ResetBasicData.reset();
LoggedSqlCollector.start();
// This will use 2 SQL queries to build this object graph
List<Order> l0 = Ebean.find(Order.class)
.setUseCache(false)
.setDisableLazyLoading(true)
.select("status, shipDate")
.fetch("details", "orderQty, unitPrice", new FetchConfig().query())
.fetch("details.product", "sku, name")
.findList();
assertThat(l0).isNotEmpty();
Order order = l0.get(0);
// normally invokes lazy loading
order.getOrderDate();
List<OrderDetail> details = order.getDetails();
OrderDetail orderDetail = details.get(0);
// normally invokes lazy loading
orderDetail.getShipQty();
List<String> loggedSql = LoggedSqlCollector.stop();
assertThat(loggedSql).hasSize(2);
String secondaryQuery = loggedSql.get(1);
assertThat(secondaryQuery).contains("select t0.order_id c0, t0.id c1,");
assertThat(secondaryQuery).contains(" from o_order_detail t0 left outer join o_product t1");
assertThat(secondaryQuery).contains(" (t0.order_id) in (?");
assertThat(secondaryQuery).contains(" order by t0.order_id, t0.id");
}
@Test
public void testJoinOnPartiallyPopulatedParent_withLazyLoadingDisabled() {
ResetBasicData.reset();
LoggedSqlCollector.start();
// This will use 2 SQL queries to build this object graph
List<Order> l0 = Ebean.find(Order.class)
.setDisableLazyLoading(true)
.select("status, shipDate")
.fetch("details", "orderQty, unitPrice")//, new FetchConfig().query())
.fetch("details.product", "sku, name")
.findList();
assertThat(l0).isNotEmpty();
Order order = l0.get(0);
// normally invokes lazy loading
order.getOrderDate();
List<OrderDetail> details = order.getDetails();
OrderDetail orderDetail = details.get(0);
// normally invokes lazy loading
orderDetail.getShipQty();
List<String> loggedSql = LoggedSqlCollector.stop();
assertThat(loggedSql).hasSize(1);
String originQuery = loggedSql.get(0);
assertThat(originQuery).contains("select t0.id c0, t0.status c1, t0.ship_date c2, t1.id c3, t1.order_qty c4, t1.unit_price c5");
assertThat(originQuery).contains(" from o_order t0 left outer join o_order_detail t1 ");
}
}