From b99f0029681d3d65af109f50e1bfa5e81b1863cc Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Mon, 14 Mar 2016 17:40:26 +1300 Subject: [PATCH] #599 - ElasticSearch - add query join support for find, findList, findPagedList, findEach, findEachWhile --- .../java/com/avaje/ebean/DocumentStore.java | 13 +++-- .../com/avaje/ebean/bean/BeanCollection.java | 5 ++ .../java/com/avaje/ebean/common/BeanList.java | 10 ++++ .../java/com/avaje/ebean/common/BeanMap.java | 9 +++ .../java/com/avaje/ebean/common/BeanSet.java | 10 ++++ .../avaje/ebean/text/json/JsonBeanReader.java | 2 +- .../ebean/text/json/JsonReadOptions.java | 17 ++++++ .../avaje/ebeaninternal/api/LoadContext.java | 56 ++++++++++--------- .../ebeaninternal/api/LoadSecondaryQuery.java | 2 +- .../server/core/DefaultServer.java | 10 ++-- .../server/core/OrmQueryRequest.java | 41 +++++++++++--- .../server/core/SpiOrmQueryRequest.java | 3 +- .../server/deploy/BeanDescriptor.java | 22 ++++++++ .../server/deploy/BeanProperty.java | 3 + .../server/deploy/BeanPropertyAssocMany.java | 21 +++++++ .../server/loadcontext/DLoadBeanContext.java | 11 ++-- .../server/loadcontext/DLoadContext.java | 4 +- .../server/loadcontext/DLoadManyContext.java | 17 ++++-- .../server/query/CQueryEngine.java | 4 +- .../query/CQueryIteratorWithBuffer.java | 3 +- .../server/querydefn/DefaultOrmQuery.java | 7 +-- .../server/text/json/DJsonBeanReader.java | 4 +- .../server/text/json/ReadJson.java | 33 +++++++---- .../docstore/api/DocQueryRequest.java | 26 +++++++++ .../docstore/none/NoneDocStore.java | 13 ++--- .../ebean/text/json/JsonBeanReaderTest.java | 2 +- .../server/deploy/BeanDescriptorTest.java | 15 +++++ 27 files changed, 276 insertions(+), 87 deletions(-) create mode 100644 src/main/java/com/avaje/ebeanservice/docstore/api/DocQueryRequest.java diff --git a/src/main/java/com/avaje/ebean/DocumentStore.java b/src/main/java/com/avaje/ebean/DocumentStore.java index d8e267d4a..f92cd518a 100644 --- a/src/main/java/com/avaje/ebean/DocumentStore.java +++ b/src/main/java/com/avaje/ebean/DocumentStore.java @@ -1,5 +1,6 @@ package com.avaje.ebean; +import com.avaje.ebeanservice.docstore.api.DocQueryRequest; import org.jetbrains.annotations.Nullable; import java.io.IOException; @@ -48,12 +49,12 @@ public interface DocumentStore { * If the document is not found null is returned. */ @Nullable - T find(Class beanType, Object id); + T find(DocQueryRequest request); /** - * Execute the query against the document store returning the list. + * Execute the find list query. This request is prepared to execute secondary queries. */ - List findList(Query query); + List findList(DocQueryRequest request); /** * Execute the query against the document store returning the paged list. @@ -61,7 +62,7 @@ public interface DocumentStore { * The query should have firstRow or maxRows set prior to calling this method. *

*/ - PagedList findPagedList(Query query); + PagedList findPagedList(DocQueryRequest request); /** * Execute the query against the document store with the expectation of a large set of results @@ -70,7 +71,7 @@ public interface DocumentStore { * For example, with the ElasticSearch doc store this uses SCROLL. *

*/ - void findEach(Query query, QueryEachConsumer consumer); + void findEach(DocQueryRequest query, QueryEachConsumer consumer); /** * Execute the query against the document store with the expectation of a large set of results @@ -82,7 +83,7 @@ public interface DocumentStore { * For example, with the ElasticSearch doc store this uses SCROLL. *

*/ - void findEachWhile(Query query, QueryEachWhileConsumer consumer); + void findEachWhile(DocQueryRequest query, QueryEachWhileConsumer consumer); /** * Process the queue entries sending updates to the document store or queuing them for later processing. diff --git a/src/main/java/com/avaje/ebean/bean/BeanCollection.java b/src/main/java/com/avaje/ebean/bean/BeanCollection.java index f9b2737d7..c24ac482b 100644 --- a/src/main/java/com/avaje/ebean/bean/BeanCollection.java +++ b/src/main/java/com/avaje/ebean/bean/BeanCollection.java @@ -31,6 +31,11 @@ public interface BeanCollection extends Serializable { ALL } + /** + * Load bean from another collection. + */ + void loadFrom(BeanCollection other); + /** * Add a bean to the list/set with modifyListen notification. */ diff --git a/src/main/java/com/avaje/ebean/common/BeanList.java b/src/main/java/com/avaje/ebean/common/BeanList.java index 1ce9aa2d3..2ac4c523d 100644 --- a/src/main/java/com/avaje/ebean/common/BeanList.java +++ b/src/main/java/com/avaje/ebean/common/BeanList.java @@ -1,5 +1,6 @@ package com.avaje.ebean.common; +import com.avaje.ebean.bean.BeanCollection; import com.avaje.ebean.bean.BeanCollectionAdd; import com.avaje.ebean.bean.BeanCollectionLoader; import com.avaje.ebean.bean.EntityBean; @@ -64,6 +65,15 @@ public final class BeanList extends AbstractBeanCollection implements List list.add((E) bean); } + @Override + @SuppressWarnings("unchecked") + public void loadFrom(BeanCollection other) { + if (list == null) { + list = new ArrayList(); + } + list.addAll((Collection) other.getActualDetails()); + } + @SuppressWarnings("unchecked") public void internalAdd(Object bean) { if (list == null) { diff --git a/src/main/java/com/avaje/ebean/common/BeanMap.java b/src/main/java/com/avaje/ebean/common/BeanMap.java index 60190e386..d31e01968 100644 --- a/src/main/java/com/avaje/ebean/common/BeanMap.java +++ b/src/main/java/com/avaje/ebean/common/BeanMap.java @@ -1,5 +1,6 @@ package com.avaje.ebean.common; +import com.avaje.ebean.bean.BeanCollection; import com.avaje.ebean.bean.BeanCollectionLoader; import com.avaje.ebean.bean.EntityBean; @@ -51,6 +52,14 @@ public final class BeanMap extends AbstractBeanCollection implements Ma return !touched && (map == null || map.isEmpty()); } + @Override + @SuppressWarnings("unchecked") + public void loadFrom(BeanCollection other) { + BeanMap otherMap = (BeanMap)other; + internalPutNull(); + map.putAll(otherMap.getActualMap()); + } + public void internalPutNull() { if (map == null) { map = new LinkedHashMap(); diff --git a/src/main/java/com/avaje/ebean/common/BeanSet.java b/src/main/java/com/avaje/ebean/common/BeanSet.java index feda082d0..8e1ed5b2f 100644 --- a/src/main/java/com/avaje/ebean/common/BeanSet.java +++ b/src/main/java/com/avaje/ebean/common/BeanSet.java @@ -1,5 +1,6 @@ package com.avaje.ebean.common; +import com.avaje.ebean.bean.BeanCollection; import com.avaje.ebean.bean.BeanCollectionAdd; import com.avaje.ebean.bean.BeanCollectionLoader; import com.avaje.ebean.bean.EntityBean; @@ -57,6 +58,15 @@ public final class BeanSet extends AbstractBeanCollection implements Set other) { + if (set == null) { + set = new LinkedHashSet(); + } + set.addAll((Collection) other.getActualDetails()); + } + @Override public void internalAddWithCheck(Object bean) { if (set == null || !set.contains(bean)) { diff --git a/src/main/java/com/avaje/ebean/text/json/JsonBeanReader.java b/src/main/java/com/avaje/ebean/text/json/JsonBeanReader.java index 6c78f089b..17a4eb027 100644 --- a/src/main/java/com/avaje/ebean/text/json/JsonBeanReader.java +++ b/src/main/java/com/avaje/ebean/text/json/JsonBeanReader.java @@ -20,7 +20,7 @@ public interface JsonBeanReader { /** * Create a new reader taking the context from the existing one but using a new JsonParser. */ - JsonBeanReader forJson(JsonParser moreJson); + JsonBeanReader forJson(JsonParser moreJson, boolean resetContext); /** * Add a bean explicitly to the persistence context. diff --git a/src/main/java/com/avaje/ebean/text/json/JsonReadOptions.java b/src/main/java/com/avaje/ebean/text/json/JsonReadOptions.java index aee1d9407..a8569d078 100644 --- a/src/main/java/com/avaje/ebean/text/json/JsonReadOptions.java +++ b/src/main/java/com/avaje/ebean/text/json/JsonReadOptions.java @@ -23,6 +23,8 @@ public class JsonReadOptions { protected PersistenceContext persistenceContext; + protected Object loadContext; + /** * Default constructor. */ @@ -100,4 +102,19 @@ public class JsonReadOptions { public PersistenceContext getPersistenceContext() { return persistenceContext; } + + /** + * Return the load context to use. + */ + public Object getLoadContext() { + return loadContext; + } + + /** + * Set the load context to use. + */ + public void setLoadContext(Object loadContext) { + this.loadContext = loadContext; + } + } \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/api/LoadContext.java b/src/main/java/com/avaje/ebeaninternal/api/LoadContext.java index 05df996b7..beac37864 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/LoadContext.java +++ b/src/main/java/com/avaje/ebeaninternal/api/LoadContext.java @@ -16,38 +16,40 @@ public interface LoadContext { */ int getSecondaryQueriesMinBatchSize(int defaultQueryBatch); - /** - * Execute any secondary (+query) queries if there are any defined. - * @param parentRequest the originating query request - */ - void executeSecondaryQueries(OrmQueryRequest parentRequest); + /** + * Execute any secondary (+query) queries if there are any defined. + * + * @param parentRequest the originating query request + * @param forEach set true when using findEach iteration + */ + void executeSecondaryQueries(OrmQueryRequest parentRequest, boolean forEach); /** - * Return the node for a given path which is used by AutoTune profiling. - */ - ObjectGraphNode getObjectGraphNode(String path); + * Return the node for a given path which is used by AutoTune profiling. + */ + ObjectGraphNode getObjectGraphNode(String path); - /** - * Return the persistence context used by this query and future lazy loading. - */ - PersistenceContext getPersistenceContext(); + /** + * Return the persistence context used by this query and future lazy loading. + */ + PersistenceContext getPersistenceContext(); - /** - * Set the persistence context used by this query and future lazy loading. - *

- * Used by query iterator when processing large result sets. - *

- */ - void resetPersistenceContext(PersistenceContext persistenceContext); + /** + * Set the persistence context used by this query and future lazy loading. + *

+ * Used by query iterator when processing large result sets. + *

+ */ + void resetPersistenceContext(PersistenceContext persistenceContext); - /** - * Register a Bean for lazy loading. - */ - void register(String path, EntityBeanIntercept ebi); + /** + * Register a Bean for lazy loading. + */ + void register(String path, EntityBeanIntercept ebi); - /** - * Register a collection for lazy loading. - */ - void register(String path, BeanCollection bc); + /** + * Register a collection for lazy loading. + */ + void register(String path, BeanCollection bc); } diff --git a/src/main/java/com/avaje/ebeaninternal/api/LoadSecondaryQuery.java b/src/main/java/com/avaje/ebeaninternal/api/LoadSecondaryQuery.java index f109d20a9..bb97cccca 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/LoadSecondaryQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/api/LoadSecondaryQuery.java @@ -14,5 +14,5 @@ public interface LoadSecondaryQuery { /** * Execute the secondary query with a given batch size. */ - void loadSecondaryQuery(OrmQueryRequest parentRequest); + void loadSecondaryQuery(OrmQueryRequest parentRequest, boolean forEach); } 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 707fc4859..92b81664a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -1124,7 +1124,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { SpiOrmQueryRequest request = createQueryRequest(spiQuery, t); if (request.isUseDocStore()) { - return docStore().find(query.getBeanType(), query.getId()); + return docStore().find(request); } try { request.initTransIfRequired(); @@ -1329,7 +1329,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } if (spiQuery.isUseDocStore()) { - return docStore().findPagedList(query); + return docStore().findPagedList(createQueryRequest(Type.LIST, query, transaction)); } return new LimitOffsetPagedList(this, spiQuery); @@ -1340,7 +1340,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { SpiOrmQueryRequest request = createQueryRequest(Type.ITERATE, query, t); if (request.isUseDocStore()) { - docStore().findEach(query, consumer); + docStore().findEach(request, consumer); return; } @@ -1353,7 +1353,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { SpiOrmQueryRequest request = createQueryRequest(Type.ITERATE, query, t); if (request.isUseDocStore()) { - docStore().findEachWhile(query, consumer); + docStore().findEachWhile(request, consumer); return; } @@ -1398,7 +1398,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return (List) result; } if (request.isUseDocStore()) { - return docStore().findList(query); + return docStore().findList(request); } try { diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java index b70c635c5..fe93f16b0 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java @@ -12,6 +12,7 @@ import com.avaje.ebean.bean.PersistenceContext; import com.avaje.ebean.event.BeanFindController; import com.avaje.ebean.event.BeanQueryAdapter; import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebean.text.json.JsonReadOptions; import com.avaje.ebeaninternal.api.BeanIdList; import com.avaje.ebeaninternal.api.CQueryPlanKey; import com.avaje.ebeaninternal.api.HashQuery; @@ -59,6 +60,8 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe private PersistenceContext persistenceContext; + private JsonReadOptions jsonRead; + private HashQuery cacheKey; private CQueryPlanKey queryPlanKey; @@ -94,8 +97,11 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe return ebeanServer.getDatabasePlatform().getLikeClause(); } - public void executeSecondaryQueries() { - loadContext.executeSecondaryQueries(this); + public void executeSecondaryQueries(boolean forEach) { + // disable lazy loading leaves loadContext null + if (loadContext != null) { + loadContext.executeSecondaryQueries(this, forEach); + } } /** @@ -152,7 +158,6 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe public void prepareQuery() { adapterPreQuery(); - this.secondaryQueries = query.convertJoins(); this.queryPlanKey = query.prepare(this); } @@ -213,9 +218,27 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe createdTransaction = true; } } - // initialise the persistenceContext and loadContext - this.persistenceContext = getPersistenceContext(query, transaction); - this.loadContext = new DLoadContext(this, secondaryQueries); + persistenceContext = getPersistenceContext(query, transaction); + loadContext = new DLoadContext(this, secondaryQueries); + } + + /** + * Return the JsonReadOptions taking into account lazy loading and persistence context. + */ + public JsonReadOptions createJsonReadOptions() { + + persistenceContext = getPersistenceContext(query, transaction); + if (query.getPersistenceContext() == null) { + query.setPersistenceContext(persistenceContext); + } + jsonRead = new JsonReadOptions(); + jsonRead.setPersistenceContext(persistenceContext); + if (!query.isDisableLazyLoading()) { + loadContext = new DLoadContext(this, secondaryQueries); + jsonRead.setLoadContext(loadContext); + } + + return jsonRead; } /** @@ -224,6 +247,10 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe public void flushPersistenceContextOnIterate() { persistenceContext = new DefaultPersistenceContext(); loadContext.resetPersistenceContext(persistenceContext); + if (jsonRead != null) { + jsonRead.setPersistenceContext(persistenceContext); + jsonRead.setLoadContext(loadContext); + } } /** @@ -239,7 +266,7 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe // determine the scope (from the query and then server) PersistenceContextScope scope = ebeanServer.getPersistenceContextScope(query); - return (scope == PersistenceContextScope.QUERY) ? new DefaultPersistenceContext() : t.getPersistenceContext(); + return (scope == PersistenceContextScope.QUERY || t == null) ? new DefaultPersistenceContext() : t.getPersistenceContext(); } /** diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java index 555dea8a1..9c4a21fef 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java @@ -7,6 +7,7 @@ import com.avaje.ebean.Version; import com.avaje.ebean.bean.BeanCollection; import com.avaje.ebeaninternal.api.SpiQuery; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeanservice.docstore.api.DocQueryRequest; import java.util.List; import java.util.Map; @@ -15,7 +16,7 @@ import java.util.Set; /** * Defines the ORM query request api. */ -public interface SpiOrmQueryRequest { +public interface SpiOrmQueryRequest extends DocQueryRequest { /** * Return the query. diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java index 8fbee99a0..485b6b234 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java @@ -107,6 +107,28 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { private final ConcurrentHashMap> comparatorCache = new ConcurrentHashMap>(); + public void merge(EntityBean bean, EntityBean existing) { + + EntityBeanIntercept fromEbi = bean._ebean_getIntercept(); + EntityBeanIntercept toEbi = existing._ebean_getIntercept(); + + int propertyLength = toEbi.getPropertyLength(); + String[] names = getProperties(); + + for (int i = 0; i < propertyLength; i++) { + + if (fromEbi.isLoadedProperty(i) ) { + BeanProperty property = getBeanProperty(names[i]); + if (!toEbi.isLoadedProperty(i)) { + Object val = property.getValue(bean); + property.setValue(existing, val); + } else if (property.isMany()) { + property.merge(bean, existing); + } + } + } + } + public enum EntityType { ORM, EMBEDDED, SQL } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java index 4b322a551..cad1459ff 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java @@ -1329,4 +1329,7 @@ public class BeanProperty implements ElPropertyValue, Property { return DocPropertyType.STRING == type && (id || discriminator); } + public void merge(EntityBean bean, EntityBean existing) { + // do nothing unless Many property + } } 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 a7514a363..02e302d18 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java @@ -201,6 +201,27 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { // by default not including "Many" properties in document store } + /** + * Copy collection value if existing is empty. + */ + @Override + public void merge(EntityBean bean, EntityBean existing) { + + Object existingCollection = getVal(existing); + if (existingCollection instanceof BeanCollection) { + BeanCollection toBC = (BeanCollection)existingCollection; + if (!toBC.isPopulated()) { + Object fromCollection = getVal(bean); + if (fromCollection instanceof BeanCollection) { + BeanCollection fromBC = (BeanCollection)fromCollection; + if (fromBC.isPopulated()) { + toBC.loadFrom(fromBC); + } + } + } + } + } + /** * Add the bean to the appropriate collection on the parent bean. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java index 59d7f39cb..575a1fc02 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java @@ -70,13 +70,12 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex return buffer; } - public void loadSecondaryQuery(OrmQueryRequest parentRequest) { + public void loadSecondaryQuery(OrmQueryRequest parentRequest, boolean forEach) { if (!queryFetch) { throw new IllegalStateException("Not expecting loadSecondaryQuery() to be called?"); } synchronized (this) { - if (bufferList != null) { for (LoadBuffer loadBuffer : bufferList) { if (!loadBuffer.list.isEmpty()) { @@ -87,8 +86,12 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex break; } } - // this is only run once - secondary query is a one shot deal - this.bufferList = null; + if (forEach) { + clear(); + } else { + // this is only run once - secondary query is a one shot deal + this.bufferList = null; + } } } } 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 7abcb9dfe..4eeb0decd 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java @@ -182,12 +182,12 @@ public class DLoadContext implements LoadContext { /** * Execute all the secondary queries. */ - public void executeSecondaryQueries(OrmQueryRequest parentRequest) { + public void executeSecondaryQueries(OrmQueryRequest parentRequest, boolean forEach) { if (secQuery != null) { for (int i = 0; i < secQuery.size(); i++) { LoadSecondaryQuery load = getLoadSecondaryQuery(secQuery.get(i).getPath()); - load.loadSecondaryQuery(parentRequest); + load.loadSecondaryQuery(parentRequest, forEach); } } } 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 c484651a2..979244593 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java @@ -21,6 +21,8 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex protected final BeanPropertyAssocMany property; + private final boolean docStoreMapped; + private List bufferList; private LoadBuffer currentBuffer; @@ -31,6 +33,7 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex super(parent, property.getBeanDescriptor(), path, defaultBatchSize, queryProps); this.property = property; + this.docStoreMapped = property.getTargetDescriptor().isDocStoreMapped(); // bufferList only required when using query joins (queryFetch) this.bufferList = (!queryFetch) ? null : new ArrayList(); this.currentBuffer = createBuffer(firstBatchSize); @@ -56,7 +59,7 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex public void configureQuery(SpiQuery query) { - parent.propagateQueryState(query, desc.isDocStoreMapped()); + parent.propagateQueryState(query, docStoreMapped); query.setParentNode(objectGraphNode); if (queryProps != null) { queryProps.configureBeanQuery(query); @@ -85,7 +88,7 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex bc.setLoader(currentBuffer); } - public void loadSecondaryQuery(OrmQueryRequest parentRequest) { + public void loadSecondaryQuery(OrmQueryRequest parentRequest, boolean forEach) { if (!queryFetch) { throw new IllegalStateException("Not expecting loadSecondaryQuery() to be called?"); @@ -103,8 +106,12 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex } } - // this is only run once - secondary query is a one shot deal - this.bufferList = null; + if (forEach) { + clear(); + } else { + // this is only run once - secondary query is a one shot deal + this.bufferList = null; + } } } } @@ -131,7 +138,7 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex @Override public boolean isUseDocStore() { - return context.parent.useDocStore; + return context.parent.useDocStore && context.docStoreMapped; } public int getBatchSize() { diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryEngine.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryEngine.java index d11d8fb1e..f2c50e3bc 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryEngine.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryEngine.java @@ -316,7 +316,7 @@ public class CQueryEngine { cquery.auditFindMany(); } - request.executeSecondaryQueries(); + request.executeSecondaryQueries(false); return beanCollection; @@ -365,7 +365,7 @@ public class CQueryEngine { cquery.auditFind(bean); } - request.executeSecondaryQueries(); + request.executeSecondaryQueries(false); return (T) bean; diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorWithBuffer.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorWithBuffer.java index ff1907b53..163fdfc2f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorWithBuffer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorWithBuffer.java @@ -42,8 +42,7 @@ class CQueryIteratorWithBuffer implements QueryIterator { moreToLoad = false; } } - // execute secondary queries - request.executeSecondaryQueries(); + request.executeSecondaryQueries(true); } return !buffer.isEmpty(); 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 2c10120c6..2967d7cae 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -462,10 +462,10 @@ public class DefaultOrmQuery implements SpiQuery { @Override public SpiQuerySecondary convertJoins() { - - createExtraJoinsToSupportManyWhereClause(); + if (!useDocStore) { + createExtraJoinsToSupportManyWhereClause(); + } markQueryJoins(); - return new OrmQuerySecondary(removeQueryJoins(), removeLazyJoins()); } @@ -845,7 +845,6 @@ public class DefaultOrmQuery implements SpiQuery { public CQueryPlanKey prepare(BeanQueryRequest request) { prepareExpressions(request); - queryPlanKey = createQueryPlanKey(); return queryPlanKey; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonBeanReader.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonBeanReader.java index 8099e9357..0497146e9 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonBeanReader.java +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonBeanReader.java @@ -45,7 +45,7 @@ public class DJsonBeanReader implements JsonBeanReader { } @Override - public JsonBeanReader forJson(JsonParser moreJson) { - return new DJsonBeanReader(desc, readJson.forJson(moreJson)); + public JsonBeanReader forJson(JsonParser moreJson, boolean resetContext) { + return new DJsonBeanReader(desc, readJson.forJson(moreJson, resetContext)); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJson.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJson.java index e2ad41484..e815de642 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJson.java +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJson.java @@ -63,21 +63,31 @@ public class ReadJson { /** * Construct when transferring load context, persistence context, object mapper etc to a new ReadJson instance. */ - private ReadJson(JsonParser moreJson, ReadJson source) { + private ReadJson(JsonParser moreJson, ReadJson source, boolean resetContext) { this.parser = moreJson; this.rootDesc = source.rootDesc; this.pathStack = source.pathStack; this.visitorMap = source.visitorMap; this.objectMapper = source.objectMapper; - this.persistenceContext = source.persistenceContext; - this.loadContext = source.loadContext; + if (resetContext) { + this.persistenceContext = new DefaultPersistenceContext(); + this.loadContext = source.loadContext; + if (loadContext != null) { + loadContext.resetPersistenceContext(persistenceContext); + } + } else { + this.persistenceContext = source.persistenceContext; + this.loadContext = source.loadContext; + } } private LoadContext initLoadContext(BeanDescriptor desc, JsonReadOptions readOptions) { - if (readOptions != null && readOptions.isEnableLazyLoading()) { + if (readOptions == null) return null; + if (readOptions.isEnableLazyLoading() && readOptions.getLoadContext() == null) { return new DLoadContext(desc, persistenceContext); + } else { + return (LoadContext) readOptions.getLoadContext(); } - return null; } private PersistenceContext initPersistenceContext(JsonReadOptions readOptions) { @@ -97,8 +107,8 @@ public class ReadJson { /** * Return a new instance of ReadJson using the existing context but with a new JsonParser. */ - public ReadJson forJson(JsonParser moreJson) { - return new ReadJson(moreJson, this); + public ReadJson forJson(JsonParser moreJson, boolean resetContext) { + return new ReadJson(moreJson, this, resetContext); } /** @@ -120,8 +130,11 @@ public class ReadJson { return null; } - Object contextBean = beanDesc.contextPutIfAbsent(persistenceContext, id, bean); - if (contextBean == null) { + Object existing = beanDesc.contextPutIfAbsent(persistenceContext, id, bean); + if (existing != null) { + beanDesc.merge(bean, (EntityBean)existing); + + } else { if (loadContext != null) { EntityBeanIntercept ebi = bean._ebean_getIntercept(); if (ebi.isPartial()) { @@ -134,7 +147,7 @@ public class ReadJson { } return null; } - return contextBean; + return existing; } /** diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/DocQueryRequest.java b/src/main/java/com/avaje/ebeanservice/docstore/api/DocQueryRequest.java new file mode 100644 index 000000000..42fa4ddd2 --- /dev/null +++ b/src/main/java/com/avaje/ebeanservice/docstore/api/DocQueryRequest.java @@ -0,0 +1,26 @@ +package com.avaje.ebeanservice.docstore.api; + +import com.avaje.ebean.text.json.JsonReadOptions; +import com.avaje.ebeaninternal.api.SpiQuery; + +/** + * A Query request for the document store. + */ +public interface DocQueryRequest { + + /** + * Return the query for this request. + */ + SpiQuery getQuery(); + + /** + * Create JsonReadOptions taking into account persistence context and lazy loading support. + */ + JsonReadOptions createJsonReadOptions(); + + /** + * Execute secondary queries. + */ + void executeSecondaryQueries(boolean forEach); + +} diff --git a/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStore.java b/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStore.java index 0b4662d10..fc09931c0 100644 --- a/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStore.java +++ b/src/main/java/com/avaje/ebeanservice/docstore/none/NoneDocStore.java @@ -6,7 +6,7 @@ import com.avaje.ebean.PagedList; import com.avaje.ebean.Query; import com.avaje.ebean.QueryEachConsumer; import com.avaje.ebean.QueryEachWhileConsumer; -import org.jetbrains.annotations.Nullable; +import com.avaje.ebeanservice.docstore.api.DocQueryRequest; import java.io.IOException; import java.util.List; @@ -60,29 +60,28 @@ public class NoneDocStore implements DocumentStore { throw implementationNotInClassPath(); } - @Nullable @Override - public T find(Class beanType, Object id) { + public T find(DocQueryRequest request) { throw implementationNotInClassPath(); } @Override - public PagedList findPagedList(Query query) { + public PagedList findPagedList(DocQueryRequest request) { throw implementationNotInClassPath(); } @Override - public List findList(Query query) { + public List findList(DocQueryRequest request) { throw implementationNotInClassPath(); } @Override - public void findEach(Query query, QueryEachConsumer consumer) { + public void findEach(DocQueryRequest query, QueryEachConsumer consumer) { throw implementationNotInClassPath(); } @Override - public void findEachWhile(Query query, QueryEachWhileConsumer consumer) { + public void findEachWhile(DocQueryRequest query, QueryEachWhileConsumer consumer) { throw implementationNotInClassPath(); } diff --git a/src/test/java/com/avaje/ebean/text/json/JsonBeanReaderTest.java b/src/test/java/com/avaje/ebean/text/json/JsonBeanReaderTest.java index ff85cd96c..b7dad8a27 100644 --- a/src/test/java/com/avaje/ebean/text/json/JsonBeanReaderTest.java +++ b/src/test/java/com/avaje/ebean/text/json/JsonBeanReaderTest.java @@ -47,7 +47,7 @@ public class JsonBeanReaderTest extends BaseTestCase { beanReader.read(); JsonParser more = getParser(); - JsonBeanReader moreReader = beanReader.forJson(more); + JsonBeanReader moreReader = beanReader.forJson(more, true); Customer customer = moreReader.read(); assertThat(customer.getId()).isEqualTo(42); diff --git a/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorTest.java b/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorTest.java index e58eaa8b1..1be4b9100 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorTest.java @@ -1,6 +1,7 @@ package com.avaje.ebeaninternal.server.deploy; import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.bean.EntityBean; import com.avaje.ebean.plugin.Property; import com.avaje.tests.model.basic.Customer; import com.avaje.tests.model.basic.Order; @@ -45,4 +46,18 @@ public class BeanDescriptorTest extends BaseTestCase { assertThat(props).extracting("name").contains("id", "status", "orderDate", "shipDate"); } + @Test + public void merge_when_empty() { + + Customer from = new Customer(); + from.setId(42); + from.setName("rob"); + + Customer to = new Customer(); + customerDesc.merge((EntityBean)from, (EntityBean)to); + + assertThat(to.getId()).isEqualTo(42); + assertThat(to.getName()).isEqualTo("rob"); + } + } \ No newline at end of file