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;