#1181 - ENH: Add @FetchPreference support - Secondary query generation is based on order of fetch statements

This commit is contained in:
Rob Bygrave
2017-11-08 23:19:58 +13:00
parent 9b2b2f7787
commit 156f505394
11 changed files with 123 additions and 11 deletions
@@ -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) {
@@ -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.
@@ -73,6 +73,8 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
final String extraWhere;
final int fetchPreference;
boolean saveRecurseSkippable;
/**
@@ -88,6 +90,7 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
this.targetType = deploy.getTargetType();
this.cascadeInfo = deploy.getCascadeInfo();
this.fetchPreference = deploy.getFetchPreference();
}
/**
@@ -109,6 +112,11 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
}
}
@Override
public int getFetchPreference() {
return fetchPreference;
}
/**
* Create a ElPropertyValue for a *ToOne or *ToMany.
*/
@@ -40,6 +40,8 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
private String docStoreDoc;
private int fetchPreference = 1000;
/**
* Construct the property.
*/
@@ -147,4 +149,11 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
return docStoreDoc;
}
public int getFetchPreference() {
return fetchPreference;
}
public void setFetchPreference(int fetchPreference) {
this.fetchPreference = fetchPreference;
}
}
@@ -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();
@@ -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())) {
@@ -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;
@@ -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();
}
@@ -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<FetchEntry> 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<FetchEntry> sortByFetchPreference(BeanDescriptor<?> desc) {
List<FetchEntry> entries = new ArrayList<>(fetchPaths.size());
int idx = 0;
for (Map.Entry<String, OrmQueryProperties> 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<Map.Entry<String, OrmQueryProperties>> entries() {
return fetchPaths.entrySet();
}
private static class FetchEntry implements Comparable<FetchEntry> {
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;
}
}
}
}