diff --git a/pom.xml b/pom.xml index 11436168e..83fcdabe6 100644 --- a/pom.xml +++ b/pom.xml @@ -92,7 +92,7 @@ io.ebean ebean-annotation - 3.3 + 3.4 diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanFkeyProperty.java b/src/main/java/io/ebeaninternal/server/deploy/BeanFkeyProperty.java index c8e8b3a8a..52bb3b4d4 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanFkeyProperty.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanFkeyProperty.java @@ -41,8 +41,10 @@ public final class BeanFkeyProperty implements ElPropertyValue { return false; } - public int getDeployOrder() { - return deployOrder; + @Override + public int getFetchPreference() { + // return some decently high value + return 1000; } private String calcPlaceHolder(String prefix, String dbColumn) { diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java b/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java index c2a720481..05f1d8d70 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java @@ -537,6 +537,12 @@ public class BeanProperty implements ElPropertyValue, Property { return dbEncryptFunction.getDecryptSql(tableAlias + "." + this.getDbColumn()); } + @Override + public int getFetchPreference() { + // return some decently high value - override on ToMany property + return 1000; + } + /** * Add any extra joins required to support this property. Generally a no * operation except for a OneToOne exported. diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java index df48456f8..6f080576c 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java @@ -73,6 +73,8 @@ public abstract class BeanPropertyAssoc extends BeanProperty { final String extraWhere; + final int fetchPreference; + boolean saveRecurseSkippable; /** @@ -88,6 +90,7 @@ public abstract class BeanPropertyAssoc extends BeanProperty { this.targetType = deploy.getTargetType(); this.cascadeInfo = deploy.getCascadeInfo(); + this.fetchPreference = deploy.getFetchPreference(); } /** @@ -109,6 +112,11 @@ public abstract class BeanPropertyAssoc extends BeanProperty { } } + @Override + public int getFetchPreference() { + return fetchPreference; + } + /** * Create a ElPropertyValue for a *ToOne or *ToMany. */ diff --git a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java index 6467e8333..e0bbc9f0e 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java +++ b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java @@ -40,6 +40,8 @@ public abstract class DeployBeanPropertyAssoc extends DeployBeanProperty { private String docStoreDoc; + private int fetchPreference = 1000; + /** * Construct the property. */ @@ -147,4 +149,11 @@ public abstract class DeployBeanPropertyAssoc extends DeployBeanProperty { return docStoreDoc; } + public int getFetchPreference() { + return fetchPreference; + } + + public void setFetchPreference(int fetchPreference) { + this.fetchPreference = fetchPreference; + } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java index 41f8b3867..2d4b72ced 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.deploy.parse; +import io.ebean.annotation.FetchPreference; import io.ebean.annotation.HistoryExclude; import io.ebean.annotation.PrivateOwned; import io.ebean.annotation.Where; @@ -90,6 +91,11 @@ class AnnotationAssocManys extends AnnotationParser { prop.setExtraWhere(where.clause()); } + FetchPreference fetchPreference = get(prop, FetchPreference.class); + if (fetchPreference != null) { + prop.setFetchPreference(fetchPreference.value()); + } + // check for manually defined joins BeanTable beanTable = prop.getBeanTable(); diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java index afccf654f..be49ab616 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.deploy.parse; +import io.ebean.annotation.FetchPreference; import io.ebean.annotation.Where; import io.ebean.config.NamingConvention; import io.ebeaninternal.server.deploy.BeanDescriptorManager; @@ -87,6 +88,11 @@ public class AnnotationAssocOnes extends AnnotationParser { prop.setExtraWhere(where.clause()); } + FetchPreference fetchPreference = get(prop, FetchPreference.class); + if (fetchPreference != null) { + prop.setFetchPreference(fetchPreference.value()); + } + if (validationAnnotations) { NotNull notNull = get(prop, NotNull.class); if (notNull != null && isEbeanValidationGroups(notNull.groups())) { diff --git a/src/main/java/io/ebeaninternal/server/el/ElPropertyChain.java b/src/main/java/io/ebeaninternal/server/el/ElPropertyChain.java index 032570fc5..5d441391c 100644 --- a/src/main/java/io/ebeaninternal/server/el/ElPropertyChain.java +++ b/src/main/java/io/ebeaninternal/server/el/ElPropertyChain.java @@ -84,6 +84,11 @@ public class ElPropertyChain implements ElPropertyValue { return "expr:" + expression + " chain:" + Arrays.toString(chain); } + @Override + public int getFetchPreference() { + return chain[0].getFetchPreference(); + } + @Override public boolean isAggregation() { return false; diff --git a/src/main/java/io/ebeaninternal/server/el/ElPropertyDeploy.java b/src/main/java/io/ebeaninternal/server/el/ElPropertyDeploy.java index 066612e09..17f4cfb29 100644 --- a/src/main/java/io/ebeaninternal/server/el/ElPropertyDeploy.java +++ b/src/main/java/io/ebeaninternal/server/el/ElPropertyDeploy.java @@ -74,4 +74,10 @@ public interface ElPropertyDeploy { * Return true if this is an aggregation property. */ boolean isAggregation(); + + /** + * Return the fetch preference. This can be used to control which ToMany relationship + * is left as a 'join' and which get converted to query join. + */ + int getFetchPreference(); } diff --git a/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryDetail.java b/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryDetail.java index 5eee0cc4b..6bdc89fd2 100644 --- a/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryDetail.java +++ b/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryDetail.java @@ -318,6 +318,10 @@ public class OrmQueryDetail implements Serializable { */ void markQueryJoins(BeanDescriptor beanDescriptor, String lazyLoadManyPath, boolean allowOne, boolean addIds) { + if (fetchPaths.isEmpty()) { + return; + } + // the name of the many fetch property if there is one String manyFetchProperty = null; @@ -325,22 +329,19 @@ public class OrmQueryDetail implements Serializable { boolean fetchJoinFirstMany = allowOne; sortFetchPaths(beanDescriptor, addIds); + List pairs = sortByFetchPreference(beanDescriptor); - for (String fetchPath : fetchPaths.keySet()) { - ElPropertyDeploy elProp = beanDescriptor.getElPropertyDeploy(fetchPath); - if (elProp == null) { - throw new PersistenceException("Invalid fetch path " + fetchPath + " from " + beanDescriptor.getFullName()); - } + for (FetchEntry pair : pairs) { + ElPropertyDeploy elProp = pair.getElProp(); if (elProp.containsManySince(manyFetchProperty)) { - // this is a join to a *ToMany - OrmQueryProperties chunk = fetchPaths.get(fetchPath); + OrmQueryProperties chunk = pair.getProperties(); if (isQueryJoinCandidate(lazyLoadManyPath, chunk)) { // this is a 'fetch join' (included in main query) if (fetchJoinFirstMany) { // letting the first one remain a 'fetch join' fetchJoinFirstMany = false; - manyFetchProperty = fetchPath; + manyFetchProperty = pair.getPath(); } else { // convert this one over to a 'query join' chunk.markForQueryJoin(); @@ -350,6 +351,25 @@ public class OrmQueryDetail implements Serializable { } } + /** + * Sort the fetch entries taking into account fetchPreference on the path. + */ + private List sortByFetchPreference(BeanDescriptor desc) { + + List entries = new ArrayList<>(fetchPaths.size()); + int idx = 0; + for (Map.Entry entry : fetchPaths.entrySet()) { + String fetchPath = entry.getKey(); + ElPropertyDeploy elProp = desc.getElPropertyDeploy(fetchPath); + if (elProp == null) { + throw new PersistenceException("Invalid fetch path " + fetchPath + " from " + desc.getFullName()); + } + entries.add(new FetchEntry(idx++, fetchPath, elProp, entry.getValue())); + } + Collections.sort(entries); + return entries; + } + /** * Return true if this path is a candidate for converting to a query join. */ @@ -483,4 +503,45 @@ public class OrmQueryDetail implements Serializable { public Set> entries() { return fetchPaths.entrySet(); } + + private static class FetchEntry implements Comparable { + + private final int index; + private final String path; + private final OrmQueryProperties properties; + private final ElPropertyDeploy elProp; + + FetchEntry(int index, String path, ElPropertyDeploy elProp, OrmQueryProperties value) { + this.index = index; + this.path = path; + this.elProp = elProp; + this.properties = value; + } + + String getPath() { + return path; + } + + OrmQueryProperties getProperties() { + return properties; + } + + ElPropertyDeploy getElProp() { + return elProp; + } + + /** + * Sort by fetchPreference and then by index order. + */ + @Override + public int compareTo(FetchEntry other) { + int fp = elProp.getFetchPreference(); + int op = other.elProp.getFetchPreference(); + if (fp == op) { + return Integer.compare(index, other.index); + } else { + return (fp < op) ? -1 : 1; + } + } + } } diff --git a/src/test/java/org/tests/model/converstation/Conversation.java b/src/test/java/org/tests/model/converstation/Conversation.java index f0caf5181..7fe9bcfdf 100644 --- a/src/test/java/org/tests/model/converstation/Conversation.java +++ b/src/test/java/org/tests/model/converstation/Conversation.java @@ -1,5 +1,6 @@ package org.tests.model.converstation; +import io.ebean.annotation.FetchPreference; import org.tests.model.BaseModel; import javax.persistence.Column; @@ -21,9 +22,11 @@ public class Conversation extends BaseModel { @ManyToOne Group group; + @FetchPreference(1) @OneToMany(mappedBy = "conversation") List participants; + @FetchPreference(2) @OneToMany(mappedBy = "conversation") List messages;