diff --git a/src/main/java/com/avaje/ebean/common/BeanList.java b/src/main/java/com/avaje/ebean/common/BeanList.java index 8207eff8e..0eb3abb7c 100644 --- a/src/main/java/com/avaje/ebean/common/BeanList.java +++ b/src/main/java/com/avaje/ebean/common/BeanList.java @@ -51,6 +51,9 @@ public final class BeanList extends AbstractBeanCollection implements List @SuppressWarnings("unchecked") public void internalAdd(Object bean) { + if (list == null) { + list = new ArrayList(); + } list.add((E) bean); } diff --git a/src/main/java/com/avaje/ebean/common/BeanMap.java b/src/main/java/com/avaje/ebean/common/BeanMap.java index ff3c3e6f2..4c0e65808 100644 --- a/src/main/java/com/avaje/ebean/common/BeanMap.java +++ b/src/main/java/com/avaje/ebean/common/BeanMap.java @@ -37,6 +37,14 @@ public final class BeanMap extends AbstractBeanCollection implements Ma super(ebeanServer, ownerBean, propertyName); } + @SuppressWarnings("unchecked") + public void internalPut(Object key, Object bean) { + if (map == null) { + map = new LinkedHashMap(); + } + map.put((K)key, (E)bean); + } + public void internalAdd(Object bean) { throw new RuntimeException("Not allowed for map"); } diff --git a/src/main/java/com/avaje/ebean/common/BeanSet.java b/src/main/java/com/avaje/ebean/common/BeanSet.java index f3a41b4dc..f04458d6b 100644 --- a/src/main/java/com/avaje/ebean/common/BeanSet.java +++ b/src/main/java/com/avaje/ebean/common/BeanSet.java @@ -45,6 +45,9 @@ public final class BeanSet extends AbstractBeanCollection implements Set(); + } set.add((E) bean); } diff --git a/src/main/java/com/avaje/ebean/meta/MetaInfoManager.java b/src/main/java/com/avaje/ebean/meta/MetaInfoManager.java index 92724dfda..a8493aea7 100644 --- a/src/main/java/com/avaje/ebean/meta/MetaInfoManager.java +++ b/src/main/java/com/avaje/ebean/meta/MetaInfoManager.java @@ -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. *

- * 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. *

* * @param reset diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionFactory.java b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionFactory.java index f0bfc101a..57cdc0726 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionFactory.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionFactory.java @@ -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); } diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java index 7a59ce06e..ffe64902a 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java @@ -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 extends Query { */ public String getLoadMode(); + /** + * This becomes a lazy loading query for a many relationship. + */ + public void setLazyLoadForParents(List parentIds, BeanPropertyAssocMany many); + + /** + * Return the lazy loading 'many' property. + */ + public BeanPropertyAssocMany getLazyLoadForParentsProperty(); + + /** + * Return the list of parent Id's for lazy loading. + */ + public List getLazyLoadForParentIds(); + /** * Set the load mode (+lazy or +query) and the load description. - * - * @param loadMode - * @param loadDescription */ public void setLoadDescription(String loadMode, String loadDescription); 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 3a4677646..f8623f419 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultBeanLoader.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultBeanLoader.java @@ -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()); 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 1a3e5c355..da9e7e25b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -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(); diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanListHelp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanListHelp.java index 72ba45f0f..c6a932e92 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanListHelp.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanListHelp.java @@ -36,6 +36,9 @@ public final class BeanListHelp implements BeanCollectionHelp { 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 implements BeanCollectionHelp { } public Object createEmpty(boolean vanilla) { - return vanilla ? new ArrayList() : new BeanList(); + if (vanilla) { + return new ArrayList(); + } + BeanList beanList = new BeanList(); + if (many != null) { + beanList.setModifyListening(many.getModifyListenMode()); + } + return beanList; } public BeanCollection createReference(Object parentBean, String propertyName) { - return new BeanList(loader, parentBean, propertyName); + BeanList beanList = new BeanList(loader, parentBean, propertyName); + beanList.setModifyListening(many.getModifyListenMode()); + return beanList; } public void refresh(EbeanServer server, Query query, Transaction t, Object parentBean) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanMapHelp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanMapHelp.java index 5ec76d500..26c4e346c 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanMapHelp.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanMapHelp.java @@ -101,22 +101,34 @@ public final class BeanMapHelp implements BeanCollectionHelp { @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 map = (Map) collection; - map.put(keyValue, bean); + ((BeanMap) collection).internalPut(keyValue, bean); } @SuppressWarnings({ "unchecked", "rawtypes" }) public BeanCollection 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) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java index b84435248..414a376b7 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java @@ -140,11 +140,23 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { } 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 extends BeanPropertyAssoc { private List findIdsByParentId(Object parentId, Transaction t, ArrayList 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 extends BeanPropertyAssoc { 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 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 bindValues = getBindParentIds(parentIds); + + query.where().raw(expr, bindValues.toArray()); + } + private List findIdsByParentIdList(List parentIdist, Transaction t, ArrayList 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 extends BeanPropertyAssoc { public Object getParentId(Object parentBean) { return descriptor.getId(parentBean); } + + public List getBindParentIds(List parentIds) { + if (exportedProperties.length == 1){ + return parentIds; + } + List expandedList = new ArrayList(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 extends BeanPropertyAssoc { 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 extends BeanPropertyAssoc { String s = inClause ? "," : " and "; sb.append(s); } - sb.append(fkColumn); + sb.append(tableAlias).append(fkColumn); if (!inClause){ sb.append("=? "); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanSetHelp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanSetHelp.java index 476a71fd3..0691d24eb 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanSetHelp.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanSetHelp.java @@ -77,17 +77,29 @@ public final class BeanSetHelp implements BeanCollectionHelp { } } + /** + * 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() : new BeanSet(); + if (vanilla) { + return new LinkedHashSet(); + } + BeanSet beanSet = new BeanSet(); + if (many != null) { + beanSet.setModifyListening(many.getModifyListenMode()); + } + return beanSet; } public BeanCollection createReference(Object parentBean, String propertyName) { - return new BeanSet(loader, parentBean, propertyName); + BeanSet beanSet = new BeanSet(loader, parentBean, propertyName); + beanSet.setModifyListening(many.getModifyListenMode()); + return beanSet; } public void refresh(EbeanServer server, Query query, Transaction t, Object parentBean) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DbReadContext.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DbReadContext.java index f7b79ef6d..09e87baf5 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/DbReadContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DbReadContext.java @@ -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. diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java index 0e773c176..4d7e85984 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java @@ -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() { 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 84c2ba26b..89b06f168 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/FilterExprPath.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/FilterExprPath.java @@ -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)); } 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 3d162083a..31aec954f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java @@ -51,23 +51,17 @@ public class DLoadContext implements LoadContext { private PersistenceContext persistenceContext; private List 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. 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 a69603bb4..a8f5ff1fa 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java @@ -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() : null; + this.bufferList = new ArrayList(); + 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 diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java index 8904cbb55..fdeeefcb2 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java @@ -84,12 +84,18 @@ public class CQuery 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 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 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 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); + } } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchIds.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchIds.java index 3fc8782a4..e277ba5f6 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchIds.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchIds.java @@ -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 } diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java index 1cf9047ef..bbddda0eb 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java @@ -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); diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java index a371f35b5..063e111c0 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java @@ -82,19 +82,22 @@ public class SqlTreeNodeBean implements SqlTreeNode { Set includedProps; final Map pathMap; - + + final BeanPropertyAssocMany lazyLoadParent; + public SqlTreeNodeBean(String prefix, BeanPropertyAssoc beanProp, SqlTreeProperties props, List 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 myChildren, boolean withId) { + SqlTreeProperties props, List 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 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()); diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyRoot.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyRoot.java index 0f00639cc..26276e111 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyRoot.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyRoot.java @@ -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 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 diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeRoot.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeRoot.java index 619842d63..eaf79e89b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeRoot.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeRoot.java @@ -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 myList, boolean withId, TableJoin includeJoin){ - super(null, null, desc, props, myList, withId); + public SqlTreeNodeRoot(BeanDescriptor desc, SqlTreeProperties props, List 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 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; + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java index 2defb4988..d8311a4ef 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -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 implements SpiQuery { */ private ObjectGraphNode parentNode; + private BeanPropertyAssocMany lazyLoadForParentsProperty; + + private List lazyLoadForParentsIds; + /** * Hash of final query after AutoFetch tuning. */ @@ -530,9 +535,25 @@ public class DefaultOrmQuery implements SpiQuery { */ public void setPersistenceContext(PersistenceContext persistenceContext) { this.persistenceContext = persistenceContext; - } + } - /** + @Override + public void setLazyLoadForParents(List parentIds, BeanPropertyAssocMany many) { + this.lazyLoadForParentsIds = parentIds; + this.lazyLoadForParentsProperty = many; + } + + @Override + public List getLazyLoadForParentIds() { + return lazyLoadForParentsIds; + } + + @Override + public BeanPropertyAssocMany getLazyLoadForParentsProperty() { + return lazyLoadForParentsProperty; + } + + /** * Return true if the query detail has neither select or joins specified. */ public boolean isDetailEmpty() { 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 def146384..0bd24548a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryProperties.java @@ -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 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. */ diff --git a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java index eee8f5eae..a7a53ccf8 100644 --- a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java @@ -34,7 +34,7 @@ public class DefaultExpressionList implements SpiExpressionList { private static final long serialVersionUID = -6992345500247035947L; - protected final ArrayList list = new ArrayList(); + protected final List list; protected final Query query; @@ -52,6 +52,11 @@ public class DefaultExpressionList implements SpiExpressionList { } public DefaultExpressionList(Query query, ExpressionFactory expr, ExpressionList parentExprList) { + this(query, expr, parentExprList, new ArrayList()); + } + + protected DefaultExpressionList(Query query, ExpressionFactory expr, ExpressionList parentExprList, List list) { + this.list = list; this.query = query; this.expr = expr; this.exprLang = expr.getLang(); diff --git a/src/main/java/com/avaje/ebeaninternal/util/FilterExpressionList.java b/src/main/java/com/avaje/ebeaninternal/util/FilterExpressionList.java index 28d08981a..4b4e3f009 100644 --- a/src/main/java/com/avaje/ebeaninternal/util/FilterExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/util/FilterExpressionList.java @@ -25,6 +25,12 @@ public class FilterExpressionList extends DefaultExpressionList { private final Query rootQuery; private final FilterExprPath pathPrefix; + + public FilterExpressionList(FilterExprPath pathPrefix, FilterExpressionList original) { + super(null, original.expr, null, original.getUnderlyingList()); + this.pathPrefix = pathPrefix; + this.rootQuery = original.rootQuery; + } public FilterExpressionList(FilterExprPath pathPrefix, ExpressionFactory expr, Query rootQuery) { super(null, expr, null); @@ -33,7 +39,7 @@ public class FilterExpressionList extends DefaultExpressionList { } public SpiExpressionList trimPath(int prefixTrim) { - return new FilterExpressionList(pathPrefix.trimPath(prefixTrim), expr, rootQuery); + return new FilterExpressionList(pathPrefix.trimPath(prefixTrim), this); } public FilterExprPath getPathPrefix() { diff --git a/src/test/java/com/avaje/ebean/BaseTestCase.java b/src/test/java/com/avaje/ebean/BaseTestCase.java index 1f716aa7a..f7d1198c8 100644 --- a/src/test/java/com/avaje/ebean/BaseTestCase.java +++ b/src/test/java/com/avaje/ebean/BaseTestCase.java @@ -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"); + } } } diff --git a/src/test/java/com/avaje/tests/basic/TestM2MVanilla.java b/src/test/java/com/avaje/tests/basic/TestM2MVanilla.java index 11dbdc45c..99505f080 100644 --- a/src/test/java/com/avaje/tests/basic/TestM2MVanilla.java +++ b/src/test/java/com/avaje/tests/basic/TestM2MVanilla.java @@ -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 userInRolesList = rolesQuery.findList(); Assert.assertTrue(userInRolesList.size() > 0); - List list = Ebean.find(MUser.class).where().in("roles", roleList).filterMany("roles") - .eq("roleName", "role1").findList(); + List list = Ebean.find(MUser.class) + .where().in("roles", roleList) + .filterMany("roles").eq("roleName", "role1") + .findList(); MUser mUser = list.get(0); List 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); diff --git a/src/test/java/com/avaje/tests/basic/encrypt/TestEncrypt.java b/src/test/java/com/avaje/tests/basic/encrypt/TestEncrypt.java index 6fcd7ca7d..1d37d9b4e 100644 --- a/src/test/java/com/avaje/tests/basic/encrypt/TestEncrypt.java +++ b/src/test/java/com/avaje/tests/basic/encrypt/TestEncrypt.java @@ -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 deleteAll = Ebean.createUpdate(EBasicEncrypt.class, "delete from EBasicEncrypt"); + deleteAll.execute(); + EBasicEncrypt e = new EBasicEncrypt(); e.setName("testname"); e.setDescription("testdesc"); diff --git a/src/test/java/com/avaje/tests/inheritance/TestSkippable.java b/src/test/java/com/avaje/tests/inheritance/TestSkippable.java index 7d56fc6fa..eb6f6e23e 100644 --- a/src/test/java/com/avaje/tests/inheritance/TestSkippable.java +++ b/src/test/java/com/avaje/tests/inheritance/TestSkippable.java @@ -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())); } diff --git a/src/test/java/com/avaje/tests/query/TestQueryFilterManyOnSecondary.java b/src/test/java/com/avaje/tests/query/TestQueryFilterManyOnSecondary.java new file mode 100644 index 000000000..dc0dfc83d --- /dev/null +++ b/src/test/java/com/avaje/tests/query/TestQueryFilterManyOnSecondary.java @@ -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 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 list = query.findList(); + for (Customer customer : list) { + List orders = customer.getOrders(); + for (Order order : orders) { + Assert.assertEquals(Order.Status.NEW, order.getStatus()); + } + } + + } + + + @Test + public void testFilterManyWithPathPredicate() { + + ResetBasicData.reset(); + + Query query = Ebean.find(Order.class) + .fetch("details", new FetchConfig().query()) + .fetch("details.product","name") + .filterMany("details").ilike("product.name", "c%") + .query(); + + List orders = query.findList(); + for (Order order : orders) { + List details = order.getDetails(); + for (OrderDetail orderDetail : details) { + Product product = orderDetail.getProduct(); + String name = product.getName(); + Assert.assertTrue(name.startsWith("C")); + } + } + } +} diff --git a/src/test/java/com/avaje/tests/query/TestQueryJoinManyNonRoot.java b/src/test/java/com/avaje/tests/query/TestQueryJoinManyNonRoot.java index 16e7e6d1b..acc1fe662 100644 --- a/src/test/java/com/avaje/tests/query/TestQueryJoinManyNonRoot.java +++ b/src/test/java/com/avaje/tests/query/TestQueryJoinManyNonRoot.java @@ -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 q = Ebean.find(Order.class).fetch("customer").fetch("customer.contacts").where() - .gt("id", 0).query(); + Query q = Ebean.find(Order.class) + .fetch("customer") + .fetch("customer.contacts") + .where().gt("id", 0).query(); List list = q.findList(); String sql = q.getGeneratedSql(); @@ -35,8 +38,12 @@ public class TestQueryJoinManyNonRoot extends BaseTestCase { ResetBasicData.reset(); - Query q = Ebean.find(Order.class).fetch("details").fetch("details.product") - .fetch("customer").fetch("customer.contacts").where().gt("id", 0).query(); + Query q = Ebean.find(Order.class) + .fetch("details") + .fetch("details.product") + .fetch("customer") + .fetch("customer.contacts") + .where().gt("id", 0).query(); List list = q.findList(); String sql = q.getGeneratedSql(); @@ -55,11 +62,19 @@ public class TestQueryJoinManyNonRoot extends BaseTestCase { ResetBasicData.reset(); - Query q = Ebean.find(Order.class).fetch("customer").fetch("customer.contacts") - .fetch("details").fetch("details.product").where().gt("id", 0).query(); + Query 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 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 ")); diff --git a/src/test/java/com/avaje/tests/query/other/TestManyLazyLoadingQuery.java b/src/test/java/com/avaje/tests/query/other/TestManyLazyLoadingQuery.java new file mode 100644 index 000000000..905798c32 --- /dev/null +++ b/src/test/java/com/avaje/tests/query/other/TestManyLazyLoadingQuery.java @@ -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 descOrder = server.getBeanDescriptor(Order.class); + BeanPropertyAssocMany beanProperty = (BeanPropertyAssocMany)descOrder.getBeanProperty("details"); + + List parentIds = new ArrayList(); + parentIds.add(1); + + + List orders = + Ebean.find(Order.class) + .where().lt("id", 4) + .findList(); + + for (Order order : orders) { + List 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 details = Ebean.find(OrderDetail.class) + .where().eq("order.id", 1) + .findList(); + + for (OrderDetail orderDetail : details) { + System.out.println(orderDetail); + } + + } + +} diff --git a/src/test/java/com/avaje/tests/query/other/TestManyToManyLazyLoading.java b/src/test/java/com/avaje/tests/query/other/TestManyToManyLazyLoading.java new file mode 100644 index 000000000..acedb06ec --- /dev/null +++ b/src/test/java/com/avaje/tests/query/other/TestManyToManyLazyLoading.java @@ -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 users = Ebean.find(MUser.class).findList(); + + for (MUser user : users) { + List 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); + } + +} diff --git a/src/test/resources/ebean.properties b/src/test/resources/ebean.properties index 0af68ce63..0f731e463 100644 --- a/src/test/resources/ebean.properties +++ b/src/test/resources/ebean.properties @@ -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=