Change *ToMany lazy loading to not use the parent entity/table

This commit is contained in:
Rob Bygrave
2014-01-30 22:27:52 +13:00
parent 87a948223c
commit 749f8d0d45
36 changed files with 542 additions and 190 deletions
@@ -51,6 +51,9 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@SuppressWarnings("unchecked")
public void internalAdd(Object bean) {
if (list == null) {
list = new ArrayList<E>();
}
list.add((E) bean);
}
@@ -37,6 +37,14 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
super(ebeanServer, ownerBean, propertyName);
}
@SuppressWarnings("unchecked")
public void internalPut(Object key, Object bean) {
if (map == null) {
map = new LinkedHashMap<K, E>();
}
map.put((K)key, (E)bean);
}
public void internalAdd(Object bean) {
throw new RuntimeException("Not allowed for map");
}
@@ -45,6 +45,9 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
@SuppressWarnings("unchecked")
public void internalAdd(Object bean) {
if (set == null) {
set = new LinkedHashSet<E>();
}
set.add((E) bean);
}
@@ -2,6 +2,9 @@ package com.avaje.ebean.meta;
import java.util.List;
/**
* Provides access to the meta data in EbeanServer such as query execution statistics.
*/
public interface MetaInfoManager {
/**
@@ -26,9 +29,9 @@ public interface MetaInfoManager {
/**
* Collect and return the ObjectGraphNode statistics.
* <p>
* These show query executions for based on an origin point and paths. This is
* used to look at the amount of lazy loading occurring for a given query
* origin point.
* These show query executions based on an origin point and relative path.
* This is used to look at the amount of lazy loading occurring for a given
* query origin point and highlight potential for tuning a query.
* </p>
*
* @param reset
@@ -8,6 +8,6 @@ public interface SpiExpressionFactory extends ExpressionFactory {
/**
* Create another expression factory with a given sub path.
*/
public ExpressionFactory createExpressionFactory(FilterExprPath prefix);
public ExpressionFactory createExpressionFactory();//FilterExprPath prefix);
}
@@ -15,6 +15,7 @@ import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.deploy.TableJoin;
import com.avaje.ebeaninternal.server.query.CancelableQuery;
import com.avaje.ebeaninternal.server.querydefn.NaturalKeyBindParam;
@@ -153,11 +154,23 @@ public interface SpiQuery<T> extends Query<T> {
*/
public String getLoadMode();
/**
* This becomes a lazy loading query for a many relationship.
*/
public void setLazyLoadForParents(List<Object> parentIds, BeanPropertyAssocMany<?> many);
/**
* Return the lazy loading 'many' property.
*/
public BeanPropertyAssocMany<?> getLazyLoadForParentsProperty();
/**
* Return the list of parent Id's for lazy loading.
*/
public List<Object> getLazyLoadForParentIds();
/**
* Set the load mode (+lazy or +query) and the load description.
*
* @param loadMode
* @param loadDescription
*/
public void setLoadDescription(String loadMode, String loadDescription);
@@ -106,20 +106,12 @@ public class DefaultBeanLoader {
BeanDescriptor<?> desc = ctx.getBeanDescriptor();
String idProperty = desc.getIdBinder().getIdProperty();
SpiQuery<?> query = (SpiQuery<?>) server.createQuery(many.getTargetType());
query.setLazyLoadForParents(idList, many);
many.addWhereParentIdIn(query, idList);
SpiQuery<?> query = (SpiQuery<?>) server.createQuery(desc.getBeanType());
query.setMode(Mode.LAZYLOAD_MANY);
query.setLazyLoadManyPath(many.getName());
query.setPersistenceContext(pc);
query.select(idProperty);
query.fetch(many.getName());
if (idList.size() == 1) {
query.where().idEq(idList.get(0));
} else {
query.where().idIn(idList);
}
String mode = loadRequest.isLazy() ? "+lazy" : "+query";
query.setLoadDescription(mode, loadRequest.getDescription());
@@ -129,7 +121,7 @@ public class DefaultBeanLoader {
if (loadRequest.isOnlyIds()) {
// override to just select the Id values
query.fetch(many.getName(), many.getTargetIdProperty());
query.select(many.getTargetIdProperty());
}
server.findList(query, loadRequest.getTransaction());
@@ -1198,7 +1198,7 @@ public final class DefaultServer implements SpiEbeanServer {
}
context.put(query.getId(), cachedBean);
DLoadContext loadContext = new DLoadContext(this, beanDescriptor, query.isReadOnly(), false, null, false);
DLoadContext loadContext = new DLoadContext(this, beanDescriptor, query.isReadOnly(), query);
loadContext.setPersistenceContext(context);
EntityBeanIntercept ebi = ((EntityBean) cachedBean)._ebean_getIntercept();
@@ -36,6 +36,9 @@ public final class BeanListHelp<T> implements BeanCollectionHelp<T> {
this.loader = loader;
}
/**
* Internal add bypassing any modify listening.
*/
public void add(BeanCollection<?> collection, Object bean) {
collection.internalAdd(bean);
}
@@ -77,12 +80,21 @@ public final class BeanListHelp<T> implements BeanCollectionHelp<T> {
}
public Object createEmpty(boolean vanilla) {
return vanilla ? new ArrayList<T>() : new BeanList<T>();
if (vanilla) {
return new ArrayList<T>();
}
BeanList<T> beanList = new BeanList<T>();
if (many != null) {
beanList.setModifyListening(many.getModifyListenMode());
}
return beanList;
}
public BeanCollection<T> createReference(Object parentBean, String propertyName) {
return new BeanList<T>(loader, parentBean, propertyName);
BeanList<T> beanList = new BeanList<T>(loader, parentBean, propertyName);
beanList.setModifyListening(many.getModifyListenMode());
return beanList;
}
public void refresh(EbeanServer server, Query<?> query, Transaction t, Object parentBean) {
@@ -101,22 +101,34 @@ public final class BeanMapHelp<T> implements BeanCollectionHelp<T> {
@SuppressWarnings("rawtypes")
public Object createEmpty(boolean vanilla) {
return vanilla ? new LinkedHashMap() : new BeanMap();
if (vanilla) {
return new LinkedHashMap();
}
BeanMap beanMap = new BeanMap();
if (many != null) {
beanMap.setModifyListening(many.getModifyListenMode());
}
return beanMap;
}
@SuppressWarnings("unchecked")
/**
* Internal add bypassing any modify listening.
*/
public void add(BeanCollection<?> collection, Object bean) {
Object keyValue = beanProperty.getValueIntercept(bean);
Map<Object, Object> map = (Map<Object, Object>) collection;
map.put(keyValue, bean);
((BeanMap<?,?>) collection).internalPut(keyValue, bean);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public BeanCollection<T> createReference(Object parentBean, String propertyName) {
return new BeanMap(loader, parentBean, propertyName);
BeanMap beanMap = new BeanMap(loader, parentBean, propertyName);
if (many != null) {
beanMap.setModifyListening(many.getModifyListenMode());
}
return beanMap;
}
public void refresh(EbeanServer server, Query<?> query, Transaction t, Object parentBean) {
@@ -140,11 +140,23 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
} else {
delStmt = "delete from "+targetDescriptor.getBaseTable()+" where ";
}
deleteByParentIdSql = delStmt + deriveWhereParentIdSql(false);
deleteByParentIdInSql = delStmt + deriveWhereParentIdSql(true);
deleteByParentIdSql = delStmt + deriveWhereParentIdSql(false,"");
deleteByParentIdInSql = delStmt + deriveWhereParentIdSql(true,"");
}
}
/**
* Add the bean to the appropriate collection on the parent bean.
*/
public void addBeanToCollectionWithCreate(Object parentBean, Object detailBean) {
BeanCollection<?> bc = (BeanCollection<?>)super.getValue(parentBean);
if (bc == null) {
bc = (BeanCollection<?>)help.createEmpty(false);
setValue(parentBean, bc);
}
help.add(bc, detailBean);
}
@Override
public Object getValue(Object bean) {
return super.getValue(bean);
@@ -196,7 +208,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
private List<Object> findIdsByParentId(Object parentId, Transaction t, ArrayList<Object> excludeDetailIds) {
String rawWhere = deriveWhereParentIdSql(false);
String rawWhere = deriveWhereParentIdSql(false,"");
EbeanServer server = getBeanDescriptor().getEbeanServer();
Query<?> q = server.find(getPropertyType())
@@ -212,9 +224,29 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
return server.findIds(q, t);
}
/**
* Add a where clause to the query for a given list of parent Id's.
*/
public void addWhereParentIdIn(SpiQuery<?> query, List<Object> parentIds) {
String tableAlias = manyToMany ? "int_." : "t0.";
if (manyToMany) {
query.setIncludeTableJoin(inverseJoin);
}
String rawWhere = deriveWhereParentIdSql(true, tableAlias);
String inClause = descriptor.getIdBinder().getIdInValueExpr(parentIds.size());
String expr = rawWhere+inClause;
// Flatten the bind values if needed (embeddedId)
List<Object> bindValues = getBindParentIds(parentIds);
query.where().raw(expr, bindValues.toArray());
}
private List<Object> findIdsByParentIdList(List<Object> parentIdist, Transaction t, ArrayList<Object> excludeDetailIds) {
String rawWhere = deriveWhereParentIdSql(true);
String rawWhere = deriveWhereParentIdSql(true,"");
String inClause = targetIdBinder.getIdInValueExpr(parentIdist.size());
String expr = rawWhere+inClause;
@@ -465,6 +497,20 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
public Object getParentId(Object parentBean) {
return descriptor.getId(parentBean);
}
public List<Object> getBindParentIds(List<Object> parentIds) {
if (exportedProperties.length == 1){
return parentIds;
}
List<Object> expandedList = new ArrayList<Object>(parentIds.size()*exportedProperties.length);
for (int i=0; i < parentIds.size(); i++) {
for (int y = 0; y < exportedProperties.length; y++) {
Object compId = parentIds.get(i);
expandedList.add(exportedProperties[y].getValue(compId));
}
}
return expandedList;
}
private void bindWhereParendId(DefaultSqlUpdate sqlUpd, Object parentId){
@@ -493,7 +539,18 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
return pos;
}
private String deriveWhereParentIdSql(boolean inClause) {
public void addSelectExported(DbSqlContext ctx, String tableAlias) {
String alias = manyToMany ? "int_" : tableAlias;
if (alias == null) {
alias = "t0";
}
for (int i = 0; i < exportedProperties.length; i++) {
ctx.appendColumn(alias, exportedProperties[i].getForeignDbColumn());
}
}
private String deriveWhereParentIdSql(boolean inClause, String tableAlias) {
StringBuilder sb = new StringBuilder();
@@ -506,7 +563,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
String s = inClause ? "," : " and ";
sb.append(s);
}
sb.append(fkColumn);
sb.append(tableAlias).append(fkColumn);
if (!inClause){
sb.append("=? ");
}
@@ -77,17 +77,29 @@ public final class BeanSetHelp<T> implements BeanCollectionHelp<T> {
}
}
/**
* Internal add bypassing any modify listening.
*/
public void add(BeanCollection<?> collection, Object bean) {
collection.internalAdd(bean);
}
public Object createEmpty(boolean vanilla) {
return vanilla ? new LinkedHashSet<T>() : new BeanSet<T>();
if (vanilla) {
return new LinkedHashSet<T>();
}
BeanSet<T> beanSet = new BeanSet<T>();
if (many != null) {
beanSet.setModifyListening(many.getModifyListenMode());
}
return beanSet;
}
public BeanCollection<T> createReference(Object parentBean, String propertyName) {
return new BeanSet<T>(loader, parentBean, propertyName);
BeanSet<T> beanSet = new BeanSet<T>(loader, parentBean, propertyName);
beanSet.setModifyListening(many.getModifyListenMode());
return beanSet;
}
public void refresh(EbeanServer server, Query<?> query, Transaction t, Object parentBean) {
@@ -72,7 +72,7 @@ public interface DbReadContext {
/**
* Set back the bean that has just been loaded with its id.
*/
public void setLoadedBean(Object loadedBean, Object id);
public void setLoadedBean(Object loadedBean, Object id, Object lazyLoadParentId);
/**
* Set back the 'detail' bean that has just been loaded.
@@ -21,18 +21,19 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
private static final Object[] EMPTY_ARRAY = new Object[] {};
private final FilterExprPath prefix;
private final FilterExprPath prefix = null;
public DefaultExpressionFactory() {
this(null);
//this();//null);
}
public DefaultExpressionFactory(FilterExprPath prefix) {
this.prefix = prefix;
}
// public DefaultExpressionFactory(FilterExprPath prefix) {
// this.prefix = prefix;
// }
public ExpressionFactory createExpressionFactory(FilterExprPath prefix) {
return new DefaultExpressionFactory(prefix);
public ExpressionFactory createExpressionFactory(){//FilterExprPath prefix) {
return this;
//return new DefaultExpressionFactory(prefix);
}
public String getLang() {
@@ -27,6 +27,9 @@ public class FilterExprPath implements Serializable {
* due to a proceeding (earlier) query join etc.
*/
public FilterExprPath trimPath(int prefixTrim) {
if (prefixTrim >= path.length()) {
return new FilterExprPath(null);
}
return new FilterExprPath(path.substring(prefixTrim));
}
@@ -51,23 +51,17 @@ public class DLoadContext implements LoadContext {
private PersistenceContext persistenceContext;
private List<OrmQueryProperties> secQuery;
public DLoadContext(SpiEbeanServer ebeanServer, BeanDescriptor<?> rootDescriptor, Boolean readOnly, SpiQuery<?> query) {
this(ebeanServer, rootDescriptor, readOnly,
Boolean.FALSE.equals(query.isUseBeanCache()),
query.getParentNode(),
query.getAutoFetchManager() != null);
}
public DLoadContext(SpiEbeanServer ebeanServer, BeanDescriptor<?> rootDescriptor, Boolean readOnly,
boolean excludeBeanCache, ObjectGraphNode parentNode, boolean useAutofetchManager) {
this.ebeanServer = ebeanServer;
this.defaultBatchSize = ebeanServer.getLazyLoadBatchSize();
this.rootDescriptor = rootDescriptor;
this.readOnly = readOnly;
this.excludeBeanCache = excludeBeanCache;
this.useAutofetchManager = useAutofetchManager;
this.excludeBeanCache = Boolean.FALSE.equals(query.isUseBeanCache());
this.useAutofetchManager = query.getAutoFetchManager() != null;
ObjectGraphNode parentNode = query.getParentNode();
if (parentNode != null){
this.origin = parentNode.getOriginQueryPoint();
this.relativePath = parentNode.getPath();
@@ -80,29 +74,29 @@ public class DLoadContext implements LoadContext {
this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, defaultBatchSize, null);
}
protected boolean isExcludeBeanCache() {
return excludeBeanCache;
protected boolean isExcludeBeanCache() {
return excludeBeanCache;
}
/**
* Return the minimum batch size when using QueryIterator with query joins.
*/
public int getSecondaryQueriesMinBatchSize(OrmQueryRequest<?> parentRequest, int defaultQueryBatch) {
if (secQuery == null) {
return -1;
}
/**
* Return the minimum batch size when using QueryIterator with query joins.
*/
public int getSecondaryQueriesMinBatchSize(OrmQueryRequest<?> parentRequest, int defaultQueryBatch) {
if (secQuery == null){
return -1;
}
int maxBatch = 0;
for (int i = 0; i < secQuery.size(); i++) {
int batchSize = secQuery.get(i).getQueryFetchBatch();
if (batchSize == 0){
batchSize = defaultQueryBatch;
}
maxBatch = Math.max(maxBatch, batchSize);
}
return maxBatch;
int maxBatch = 0;
for (int i = 0; i < secQuery.size(); i++) {
int batchSize = secQuery.get(i).getQueryFetchBatch();
if (batchSize == 0) {
batchSize = defaultQueryBatch;
}
maxBatch = Math.max(maxBatch, batchSize);
}
return maxBatch;
}
/**
* Execute all the secondary queries.
@@ -30,15 +30,13 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
super(parent, property.getBeanDescriptor(), path, defaultBatchSize, queryProps);
this.property = property;
this.currentBuffer = createBuffer(firstBatchSize);
this.bufferList = queryFetch ? new ArrayList<DLoadManyContext.LoadBuffer>() : null;
this.bufferList = new ArrayList<DLoadManyContext.LoadBuffer>();
this.currentBuffer = createBuffer(firstBatchSize);
}
private LoadBuffer createBuffer(int size) {
LoadBuffer buffer = new LoadBuffer(this, size);
if (bufferList != null) {
bufferList.add(buffer);
}
bufferList.add(buffer);
return buffer;
}
@@ -48,10 +46,10 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
if (parent.isReadOnly() != null){
query.setReadOnly(parent.isReadOnly());
}
query.setParentNode(getObjectGraphNode());
query.setParentNode(objectGraphNode);
if (queryProps != null){
queryProps.configureManyQuery(query);
queryProps.configureBeanQuery(query);
}
if (parent.isUseAutofetchManager()){
@@ -59,20 +57,6 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
}
}
public ObjectGraphNode getObjectGraphNode() {
// we return the parent node ... as we actually
// query on the parent selecting just it's id
int pos = path.lastIndexOf('.');
if (pos == -1){
return parent.getObjectGraphNode(null);
} else {
String parentPath = path.substring(0, pos);
return parent.getObjectGraphNode(parentPath);
}
}
public BeanPropertyAssocMany<?> getBeanProperty() {
return property;
}
@@ -156,7 +140,7 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
@Override
public ObjectGraphNode getObjectGraphNode() {
return context.getObjectGraphNode();
return context.objectGraphNode;
}
@Override
@@ -84,12 +84,18 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
/**
* Flag set when 'master' bean changed.
*/
boolean loadedBeanChanged;
private boolean loadedBeanChanged;
/**
* The 'master' bean just loaded.
*/
private Object loadedBean;
private final BeanPropertyAssocMany<?> lazyLoadManyProperty;
private Object lazyLoadParentId;
private Object lazyLoadParentBean;
/**
* Holds the previous loaded bean.
*/
@@ -234,6 +240,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
this.queryPlan = queryPlan;
this.query = request.getQuery();
this.queryMode = query.getMode();
this.lazyLoadManyProperty = query.getLazyLoadForParentsProperty();
this.readOnly = request.isReadOnly();
@@ -438,7 +445,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
return persistenceContext;
}
public void setLoadedBean(Object bean, Object id) {
public void setLoadedBean(Object bean, Object id, Object lazyLoadParentId) {
if (id != null && id.equals(loadedBeanId)) {
// master/detail loading with master bean
// unchanged. NB Using id to avoid any issue
@@ -452,7 +459,19 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
this.prevLoadedBean = loadedBean;
this.loadedBeanId = id;
}
this.loadedBean = bean;
if (lazyLoadParentId != null) {
if (!lazyLoadParentId.equals(this.lazyLoadParentId)) {
// get the appropriate parent bean from the persistence context
this.lazyLoadParentBean = persistenceContext.get(lazyLoadManyProperty.getBeanDescriptor().getBeanType(), lazyLoadParentId);
this.lazyLoadParentId = lazyLoadParentId;
}
// add the loadedBean to the appropriate collection of lazyLoadParentBean
lazyLoadManyProperty.addBeanToCollectionWithCreate(lazyLoadParentBean, loadedBean);
}
}
}
@@ -299,7 +299,7 @@ public class CQueryFetchIds {
// no-op
}
public void setLoadedBean(Object loadedBean, Object id) {
public void setLoadedBean(Object loadedBean, Object id, Object lazyLoadParentId) {
// no-op
}
@@ -24,6 +24,7 @@ import com.avaje.ebeaninternal.server.deploy.TableJoin;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -256,7 +257,10 @@ public class SqlTreeBuilder {
if (prefix == null) {
buildExtraJoins(desc, myList);
return new SqlTreeNodeRoot(desc, props, myList, !subQuery, includeJoin);
// Optional many property for lazy loading query
BeanPropertyAssocMany<?> lazyLoadMany = (query == null) ? null : query.getLazyLoadForParentsProperty();
return new SqlTreeNodeRoot(desc, props, myList, !subQuery, includeJoin, lazyLoadMany);
} else if (prop instanceof BeanPropertyAssocMany<?>) {
return new SqlTreeNodeManyRoot(prefix, (BeanPropertyAssocMany<?>) prop, props, myList);
@@ -82,19 +82,22 @@ public class SqlTreeNodeBean implements SqlTreeNode {
Set<String> includedProps;
final Map<String, String> pathMap;
final BeanPropertyAssocMany<?> lazyLoadParent;
public SqlTreeNodeBean(String prefix, BeanPropertyAssoc<?> beanProp, SqlTreeProperties props,
List<SqlTreeNode> myChildren, boolean withId) {
this(prefix, beanProp, beanProp.getTargetDescriptor(), props, myChildren, withId);
this(prefix, beanProp, beanProp.getTargetDescriptor(), props, myChildren, withId, null);
}
/**
* Create with the appropriate node.
*/
public SqlTreeNodeBean(String prefix, BeanPropertyAssoc<?> beanProp, BeanDescriptor<?> desc,
SqlTreeProperties props, List<SqlTreeNode> myChildren, boolean withId) {
SqlTreeProperties props, List<SqlTreeNode> myChildren, boolean withId, BeanPropertyAssocMany<?> lazyLoadParent) {
this.lazyLoadParent = lazyLoadParent;
this.prefix = prefix;
this.nodeBeanProp = beanProp;
this.desc = desc;
@@ -156,7 +159,7 @@ public class SqlTreeNodeBean implements SqlTreeNode {
}
}
protected void postLoad(DbReadContext cquery, Object loadedBean, Object id) {
protected void postLoad(DbReadContext cquery, Object loadedBean, Object id, Object lazyLoadParentId) {
}
public void buildSelectExpressionChain(List<String> selectChain) {
@@ -179,6 +182,11 @@ public class SqlTreeNodeBean implements SqlTreeNode {
*/
public void load(DbReadContext ctx, Object parentBean) throws SQLException {
Object lazyLoadParentId = null;
if (lazyLoadParent != null) {
lazyLoadParentId = lazyLoadParent.getBeanDescriptor().getIdBinder().read(ctx);
}
// bean already existing in the persistence context
Object contextBean = null;
@@ -333,13 +341,13 @@ public class SqlTreeNodeBean implements SqlTreeNode {
if (!readId) {
// a bean with no Id (never found in context)
postLoad(ctx, localBean, id);
postLoad(ctx, localBean, id, null);
} else {
// return the contextBean which is either the localBean
// read from the resultSet and put into the context OR
// the 'matching' bean that already existed in the context
postLoad(ctx, contextBean, id);
postLoad(ctx, contextBean, id, lazyLoadParentId);
}
}
@@ -375,6 +383,10 @@ public class SqlTreeNodeBean implements SqlTreeNode {
ctx.pushJoin(prefix);
ctx.pushTableAlias(prefix);
if (lazyLoadParent != null) {
lazyLoadParent.addSelectExported(ctx, prefix);
}
if (!subQuery && inheritInfo != null) {
ctx.appendColumn(inheritInfo.getDiscriminatorColumn());
@@ -10,11 +10,11 @@ import com.avaje.ebeaninternal.server.deploy.DbSqlContext;
public final class SqlTreeNodeManyRoot extends SqlTreeNodeBean {
public SqlTreeNodeManyRoot(String prefix, BeanPropertyAssocMany<?> prop, SqlTreeProperties props, List<SqlTreeNode> myList) {
super(prefix, prop, prop.getTargetDescriptor(), props, myList, true);
super(prefix, prop, prop.getTargetDescriptor(), props, myList, true, null);
}
@Override
protected void postLoad(DbReadContext cquery, Object loadedBean, Object id) {
protected void postLoad(DbReadContext cquery, Object loadedBean, Object id, Object lazyLoadParentId) {
// put the localBean into the manyValue so that it
// is added to the collection/map
@@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.query;
import java.util.List;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.deploy.DbReadContext;
import com.avaje.ebeaninternal.server.deploy.DbSqlContext;
import com.avaje.ebeaninternal.server.deploy.TableJoin;
@@ -13,43 +14,43 @@ import com.avaje.ebeaninternal.server.deploy.TableJoin;
public final class SqlTreeNodeRoot extends SqlTreeNodeBean {
private final TableJoin includeJoin;
/**
* Specify for SqlSelect to include an Id property or not.
*/
public SqlTreeNodeRoot(BeanDescriptor<?> desc, SqlTreeProperties props, List<SqlTreeNode> myList, boolean withId, TableJoin includeJoin){
super(null, null, desc, props, myList, withId);
public SqlTreeNodeRoot(BeanDescriptor<?> desc, SqlTreeProperties props, List<SqlTreeNode> myList, boolean withId, TableJoin includeJoin, BeanPropertyAssocMany<?> many){
super(null, null, desc, props, myList, withId, many);
this.includeJoin = includeJoin;
}
public SqlTreeNodeRoot(BeanDescriptor<?> desc, SqlTreeProperties props, List<SqlTreeNode> myList, boolean withId) {
super(null, null, desc, props, myList, withId);
super(null, null, desc, props, myList, withId, null);
this.includeJoin = null;
}
@Override
protected void postLoad(DbReadContext cquery, Object loadedBean, Object id) {
protected void postLoad(DbReadContext cquery, Object loadedBean, Object id, Object lazyLoadParentId) {
// set the current bean with id...
cquery.setLoadedBean(loadedBean, id);
cquery.setLoadedBean(loadedBean, id, lazyLoadParentId);
}
/**
* For the root node there is no join type or on clause etc.
*/
@Override
public boolean appendFromBaseTable(DbSqlContext ctx, boolean forceOuterJoin) {
ctx.append(desc.getBaseTable());
ctx.append(" ").append(ctx.getTableAlias(null));
if (includeJoin != null){
String a1 = ctx.getTableAlias(null);
String a2 = "int_"; // unique alias for intersection join
includeJoin.addJoin(forceOuterJoin, a1, a2, ctx);
}
return forceOuterJoin;
}
/**
* For the root node there is no join type or on clause etc.
*/
@Override
public boolean appendFromBaseTable(DbSqlContext ctx, boolean forceOuterJoin) {
ctx.append(desc.getBaseTable());
ctx.append(" ").append(ctx.getTableAlias(null));
if (includeJoin != null) {
String a1 = ctx.getTableAlias(null);
String a2 = "int_"; // unique alias for intersection join
includeJoin.addJoin(forceOuterJoin, a1, a2, ctx);
}
return forceOuterJoin;
}
}
@@ -42,6 +42,7 @@ import com.avaje.ebeaninternal.api.SpiExpressionList;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.deploy.DRawSqlSelect;
import com.avaje.ebeaninternal.server.deploy.DeployNamedQuery;
import com.avaje.ebeaninternal.server.deploy.TableJoin;
@@ -207,6 +208,10 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
*/
private ObjectGraphNode parentNode;
private BeanPropertyAssocMany<?> lazyLoadForParentsProperty;
private List<Object> lazyLoadForParentsIds;
/**
* Hash of final query after AutoFetch tuning.
*/
@@ -530,9 +535,25 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
*/
public void setPersistenceContext(PersistenceContext persistenceContext) {
this.persistenceContext = persistenceContext;
}
}
/**
@Override
public void setLazyLoadForParents(List<Object> parentIds, BeanPropertyAssocMany<?> many) {
this.lazyLoadForParentsIds = parentIds;
this.lazyLoadForParentsProperty = many;
}
@Override
public List<Object> getLazyLoadForParentIds() {
return lazyLoadForParentsIds;
}
@Override
public BeanPropertyAssocMany<?> getLazyLoadForParentsProperty() {
return lazyLoadForParentsProperty;
}
/**
* Return true if the query detail has neither select or joins specified.
*/
public boolean isDetailEmpty() {
@@ -15,6 +15,7 @@ import com.avaje.ebean.OrderBy;
import com.avaje.ebean.Query;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionFactory;
import com.avaje.ebeaninternal.api.SpiExpressionList;
import com.avaje.ebeaninternal.api.SpiQuery;
@@ -170,7 +171,7 @@ public class OrmQueryProperties implements Serializable {
if (filterMany == null){
FilterExprPath exprPath = new FilterExprPath(path);
SpiExpressionFactory queryEf = (SpiExpressionFactory)rootQuery.getExpressionFactory();
ExpressionFactory filterEf = queryEf.createExpressionFactory(exprPath);
ExpressionFactory filterEf = queryEf.createExpressionFactory();//exprPath);
filterMany = new FilterExpressionList(exprPath, filterEf, rootQuery);
// by default we need to make this a 'query join' now
queryFetchAll = true;
@@ -229,12 +230,17 @@ public class OrmQueryProperties implements Serializable {
public void configureBeanQuery(SpiQuery<?> query) {
if (trimmedProperties != null && trimmedProperties.length() > 0) {
query.select(trimmedProperties);
if (filterMany != null){
query.setFilterMany(path, filterMany);
}
query.select(trimmedProperties);
}
if (filterMany != null){
SpiExpressionList<?> trimPath = filterMany.trimPath(path.length()+1);
List<SpiExpression> underlyingList = trimPath.getUnderlyingList();
for (SpiExpression spiExpression : underlyingList) {
query.where().add(spiExpression);
}
}
if (secondaryChildren != null) {
int trimPath = path.length() + 1;
for (int i = 0; i < secondaryChildren.size(); i++) {
@@ -251,36 +257,6 @@ public class OrmQueryProperties implements Serializable {
}
}
/**
* Define the select and joins for this query.
*/
@SuppressWarnings("unchecked")
public void configureManyQuery(SpiQuery<?> query) {
if (trimmedProperties != null && trimmedProperties.length() > 0) {
query.fetch(query.getLazyLoadManyPath(), trimmedProperties);
}
if (filterMany != null){
query.setFilterMany(path, filterMany);
}
if (secondaryChildren != null) {
int trimlen = path.length() - query.getLazyLoadManyPath().length();
for (int i = 0; i < secondaryChildren.size(); i++) {
OrmQueryProperties p = secondaryChildren.get(i);
String path = p.getPath();
path = path.substring(trimlen);
query.fetch(path, p.getProperties(), p.getFetchConfig());
query.setFilterMany(path, p.getFilterManyTrimPath(trimlen));
}
}
if (orderBy != null){
query.setOrder(orderBy);
}
}
/**
* Creates a copy of the OrmQueryProperties.
*/
@@ -34,7 +34,7 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
private static final long serialVersionUID = -6992345500247035947L;
protected final ArrayList<SpiExpression> list = new ArrayList<SpiExpression>();
protected final List<SpiExpression> list;
protected final Query<T> query;
@@ -52,6 +52,11 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
}
public DefaultExpressionList(Query<T> query, ExpressionFactory expr, ExpressionList<T> parentExprList) {
this(query, expr, parentExprList, new ArrayList<SpiExpression>());
}
protected DefaultExpressionList(Query<T> query, ExpressionFactory expr, ExpressionList<T> parentExprList, List<SpiExpression> list) {
this.list = list;
this.query = query;
this.expr = expr;
this.exprLang = expr.getLang();
@@ -25,6 +25,12 @@ public class FilterExpressionList<T> extends DefaultExpressionList<T> {
private final Query<T> rootQuery;
private final FilterExprPath pathPrefix;
public FilterExpressionList(FilterExprPath pathPrefix, FilterExpressionList<T> original) {
super(null, original.expr, null, original.getUnderlyingList());
this.pathPrefix = pathPrefix;
this.rootQuery = original.rootQuery;
}
public FilterExpressionList(FilterExprPath pathPrefix, ExpressionFactory expr, Query<T> rootQuery) {
super(null, expr, null);
@@ -33,7 +39,7 @@ public class FilterExpressionList<T> extends DefaultExpressionList<T> {
}
public SpiExpressionList<?> trimPath(int prefixTrim) {
return new FilterExpressionList<T>(pathPrefix.trimPath(prefixTrim), expr, rootQuery);
return new FilterExpressionList<T>(pathPrefix.trimPath(prefixTrim), this);
}
public FilterExprPath getPathPrefix() {
@@ -1,7 +1,6 @@
package com.avaje.ebean;
import org.avaje.agentloader.AgentLoader;
import org.junit.BeforeClass;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -9,10 +8,11 @@ public class BaseTestCase {
protected static Logger logger = LoggerFactory.getLogger(BaseTestCase.class);
@BeforeClass
public static void preStart() {
static {
logger.debug("... preStart");
AgentLoader.loadAgentFromClasspath("avaje-ebeanorm-agent","debug=0");
if (!AgentLoader.loadAgentFromClasspath("avaje-ebeanorm-agent","debug=0")) {
logger.info("avaje-ebeanorm-agent not found in classpath - not dynamically loaded");
}
}
}
@@ -10,6 +10,7 @@ import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.Query;
import com.avaje.ebean.SqlUpdate;
import com.avaje.tests.model.basic.MRole;
import com.avaje.tests.model.basic.MUser;
@@ -18,6 +19,14 @@ public class TestM2MVanilla extends BaseTestCase {
@Test
public void testVanilla() {
SqlUpdate delInt = Ebean.createSqlUpdate("delete from mrole_muser");
SqlUpdate delRoles = Ebean.createSqlUpdate("delete from mrole");
SqlUpdate delUsers = Ebean.createSqlUpdate("delete from muser");
Ebean.execute(delInt);
Ebean.execute(delRoles);
Ebean.execute(delUsers);
MRole r1 = new MRole();
r1.setRoleName("role1");
Ebean.save(r1);
@@ -70,15 +79,17 @@ public class TestM2MVanilla extends BaseTestCase {
List<MUser> userInRolesList = rolesQuery.findList();
Assert.assertTrue(userInRolesList.size() > 0);
List<MUser> list = Ebean.find(MUser.class).where().in("roles", roleList).filterMany("roles")
.eq("roleName", "role1").findList();
List<MUser> list = Ebean.find(MUser.class)
.where().in("roles", roleList)
.filterMany("roles").eq("roleName", "role1")
.findList();
MUser mUser = list.get(0);
List<MRole> roles = mUser.getRoles();
Assert.assertEquals(1, roles.size());
Ebean.refreshMany(mUser, "roles");
Assert.assertEquals(1, mUser.getRoles().size());
// Ebean.refreshMany(mUser, "roles");
// Assert.assertEquals(1, mUser.getRoles().size());
checkRoles2.remove(0);
checkRoles2.remove(0);
@@ -10,6 +10,7 @@ import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.SqlQuery;
import com.avaje.ebean.SqlRow;
import com.avaje.ebean.Update;
import com.avaje.ebean.config.dbplatform.DbEncrypt;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.tests.model.basic.EBasicEncrypt;
@@ -19,6 +20,9 @@ public class TestEncrypt extends BaseTestCase {
@Test
public void test() {
Update<EBasicEncrypt> deleteAll = Ebean.createUpdate(EBasicEncrypt.class, "delete from EBasicEncrypt");
deleteAll.execute();
EBasicEncrypt e = new EBasicEncrypt();
e.setName("testname");
e.setDescription("testdesc");
@@ -61,6 +61,7 @@ public class TestSkippable extends TestCase {
final ListAttributeValue value2_DB_2 = listAttributeDB_2.getValues().iterator().next();
Assert.assertEquals(value2.getId(), value2_DB_2.getId());
Assert.assertTrue("Cascade failed", value2.getId().equals(value2_DB_2.getId()));
}
@@ -0,0 +1,63 @@
package com.avaje.tests.query;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.FetchConfig;
import com.avaje.ebean.Query;
import com.avaje.tests.model.basic.Customer;
import com.avaje.tests.model.basic.Order;
import com.avaje.tests.model.basic.OrderDetail;
import com.avaje.tests.model.basic.Product;
import com.avaje.tests.model.basic.ResetBasicData;
public class TestQueryFilterManyOnSecondary extends BaseTestCase {
@Test
public void testFilterManyWithSimplePredicate() {
ResetBasicData.reset();
Query<Customer> query = Ebean.find(Customer.class)
.fetch("orders", new FetchConfig().query())
.where().ilike("name", "Rob%").gt("id", 0)
.filterMany("orders").eq("status", Order.Status.NEW)
.query();
List<Customer> list = query.findList();
for (Customer customer : list) {
List<Order> orders = customer.getOrders();
for (Order order : orders) {
Assert.assertEquals(Order.Status.NEW, order.getStatus());
}
}
}
@Test
public void testFilterManyWithPathPredicate() {
ResetBasicData.reset();
Query<Order> query = Ebean.find(Order.class)
.fetch("details", new FetchConfig().query())
.fetch("details.product","name")
.filterMany("details").ilike("product.name", "c%")
.query();
List<Order> orders = query.findList();
for (Order order : orders) {
List<OrderDetail> details = order.getDetails();
for (OrderDetail orderDetail : details) {
Product product = orderDetail.getProduct();
String name = product.getName();
Assert.assertTrue(name.startsWith("C"));
}
}
}
}
@@ -7,6 +7,7 @@ import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.FetchConfig;
import com.avaje.ebean.Query;
import com.avaje.tests.model.basic.Order;
import com.avaje.tests.model.basic.ResetBasicData;
@@ -18,8 +19,10 @@ public class TestQueryJoinManyNonRoot extends BaseTestCase {
ResetBasicData.reset();
Query<Order> q = Ebean.find(Order.class).fetch("customer").fetch("customer.contacts").where()
.gt("id", 0).query();
Query<Order> q = Ebean.find(Order.class)
.fetch("customer")
.fetch("customer.contacts")
.where().gt("id", 0).query();
List<Order> list = q.findList();
String sql = q.getGeneratedSql();
@@ -35,8 +38,12 @@ public class TestQueryJoinManyNonRoot extends BaseTestCase {
ResetBasicData.reset();
Query<Order> q = Ebean.find(Order.class).fetch("details").fetch("details.product")
.fetch("customer").fetch("customer.contacts").where().gt("id", 0).query();
Query<Order> q = Ebean.find(Order.class)
.fetch("details")
.fetch("details.product")
.fetch("customer")
.fetch("customer.contacts")
.where().gt("id", 0).query();
List<Order> list = q.findList();
String sql = q.getGeneratedSql();
@@ -55,11 +62,19 @@ public class TestQueryJoinManyNonRoot extends BaseTestCase {
ResetBasicData.reset();
Query<Order> q = Ebean.find(Order.class).fetch("customer").fetch("customer.contacts")
.fetch("details").fetch("details.product").where().gt("id", 0).query();
Query<Order> q = Ebean.find(Order.class)
.fetch("customer")
.fetch("customer.contacts")
.fetch("details", new FetchConfig().query(10))
.fetch("details.product")
.where().gt("id", 0).query();
List<Order> list = q.findList();
String sql = q.getGeneratedSql();
for (Order order : list) {
order.getCustomer().getContacts().size();
}
Assert.assertTrue(list.size() > 0);
Assert.assertTrue(sql.contains("join o_customer t1 on t1.id "));
@@ -0,0 +1,75 @@
package com.avaje.tests.query.other;
import java.util.ArrayList;
import java.util.List;
import javax.validation.constraints.AssertTrue;
import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.tests.model.basic.Order;
import com.avaje.tests.model.basic.OrderDetail;
import com.avaje.tests.model.basic.ResetBasicData;
public class TestManyLazyLoadingQuery extends BaseTestCase {
@Test
public void test() {
ResetBasicData.reset();
SpiEbeanServer server = (SpiEbeanServer)Ebean.getServer(null);
BeanDescriptor<Order> descOrder = server.getBeanDescriptor(Order.class);
BeanPropertyAssocMany<?> beanProperty = (BeanPropertyAssocMany<?>)descOrder.getBeanProperty("details");
List<Object> parentIds = new ArrayList<Object>();
parentIds.add(1);
List<Order> orders =
Ebean.find(Order.class)
.where().lt("id", 4)
.findList();
for (Order order : orders) {
List<OrderDetail> details = order.getDetails();
System.out.println(details.size());
}
// start transaction to keep PC going to lazy query
Ebean.beginTransaction();
try {
Ebean.find(Order.class, 1);
SpiQuery<?> query0 = (SpiQuery<?>)Ebean.find(OrderDetail.class);
query0.setLazyLoadForParents(parentIds, beanProperty);
beanProperty.addWhereParentIdIn(query0, parentIds);
List<?> list0 = query0.findList();
} finally {
Ebean.endTransaction();
}
List<OrderDetail> details = Ebean.find(OrderDetail.class)
.where().eq("order.id", 1)
.findList();
for (OrderDetail orderDetail : details) {
System.out.println(orderDetail);
}
}
}
@@ -0,0 +1,42 @@
package com.avaje.tests.query.other;
import java.util.List;
import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.tests.model.basic.MRole;
import com.avaje.tests.model.basic.MUser;
public class TestManyToManyLazyLoading extends BaseTestCase {
@Test
public void test() {
createData();
List<MUser> users = Ebean.find(MUser.class).findList();
for (MUser user : users) {
List<MRole> roles = user.getRoles();
System.out.println(""+roles.size());
}
}
private void createData() {
MRole r0 = new MRole("r0");
MRole r1 = new MRole("r1");
Ebean.save(r0);
Ebean.save(r1);
MUser u0 = new MUser("usr0");
u0.addRole(r0);
u0.addRole(r1);
Ebean.save(u0);
}
}
+3 -3
View File
@@ -24,8 +24,8 @@ ebean.autofetch.profiling.base=10
ebean.autofetch.traceUsageCollection=false
#ebean.ddl.generate=true
#ebean.ddl.run=true
ebean.ddl.generate=true
ebean.ddl.run=true
ebean.debug.sql=true
@@ -81,7 +81,7 @@ ebean.cacheWarmingDelay=-1
## DataSources (If using default Ebean DataSourceFactory)
## -------------------------------------------------------------
datasource.default=mysql
datasource.default=h2
datasource.h2.username=sa
datasource.h2.password=