diff --git a/README.md b/README.md index 30dc2c06b..a560d8c82 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,3 @@ Goto [https://ebean.io/docs/](https://ebean.io/docs/) [Maven central - all related projects](http://search.maven.org/#search%7Cga%7C1%7Cebean "maven central all related projects") -## Other versions -* [![Maven Central : ebean-agent](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-agent/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-agent) - ebean-agent -* [![Maven Central : ebean-maven-plugin](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-maven-plugin/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-maven-plugin) - ebean-maven-plugin diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java index 45a5b5a11..1870c1b6f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java @@ -193,7 +193,7 @@ public final class CQuery implements DbReadContext, CancelableQuery, SpiProfi // set the generated sql back to the query // so its available to the user... query.setGeneratedSql(queryPlan.getSql()); - SqlTree sqlTree = queryPlan.getSqlTree(); + SqlTreePlan sqlTree = queryPlan.getSqlTree(); this.rootNode = sqlTree.getRootNode(); this.manyProperty = sqlTree.getManyProperty(); this.sql = queryPlan.getSql(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java index 278603f5b..3367fbfe4 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java @@ -110,7 +110,7 @@ final class CQueryBuilder { sql = buildUpdateSql(request, rootTableAlias, predicates, sqlTree); } // cache the query plan - queryPlan = new CQueryPlan(request, sql, sqlTree, predicates.getLogWhereSql()); + queryPlan = new CQueryPlan(request, sql, sqlTree.plan(), predicates.getLogWhereSql()); request.putQueryPlan(queryPlan); return new CQueryUpdate(request, predicates, queryPlan); } @@ -197,7 +197,7 @@ final class CQueryBuilder { SqlTree sqlTree = createSqlTree(request, predicates); SqlLimitResponse s = buildSql(null, request, predicates, sqlTree); - queryPlan = new CQueryPlan(request, s.getSql(), sqlTree, predicates.getLogWhereSql()); + queryPlan = new CQueryPlan(request, s.getSql(), sqlTree.plan(), predicates.getLogWhereSql()); request.putQueryPlan(queryPlan); return new CQueryFetchSingleAttribute(request, predicates, queryPlan, query.isCountDistinct()); } @@ -288,7 +288,7 @@ final class CQueryBuilder { } } // cache the query plan - queryPlan = new CQueryPlan(request, sql, sqlTree, predicates.getLogWhereSql()); + queryPlan = new CQueryPlan(request, sql, sqlTree.plan(), predicates.getLogWhereSql()); request.putQueryPlan(queryPlan); return new CQueryRowCount(queryPlan, request, predicates); } @@ -343,7 +343,7 @@ final class CQueryBuilder { if (rawSql) { queryPlan = new CQueryPlanRawSql(request, res, sqlTree, predicates.getLogWhereSql()); } else { - queryPlan = new CQueryPlan(request, res, sqlTree, false, predicates.getLogWhereSql()); + queryPlan = new CQueryPlan(request, res, sqlTree.plan(), false, predicates.getLogWhereSql()); } BeanDescriptor desc = request.descriptor(); if (desc.isReadAuditing()) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java index bf84070f2..d07276920 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java @@ -55,7 +55,7 @@ public class CQueryPlan implements SpiQueryPlan { private final String sql; private final String hash; private final String logWhereSql; - private final SqlTree sqlTree; + private final SqlTreePlan sqlTree; /** * Encrypted properties required additional binding. @@ -76,7 +76,7 @@ public class CQueryPlan implements SpiQueryPlan { /** * Create a query plan based on a OrmQueryRequest. */ - CQueryPlan(OrmQueryRequest request, SqlLimitResponse sqlRes, SqlTree sqlTree, boolean rawSql, String logWhereSql) { + CQueryPlan(OrmQueryRequest request, SqlLimitResponse sqlRes, SqlTreePlan sqlTree, boolean rawSql, String logWhereSql) { this.server = request.server(); this.dataTimeZone = server.dataTimeZone(); this.beanType = request.descriptor().type(); @@ -101,7 +101,7 @@ public class CQueryPlan implements SpiQueryPlan { /** * Create a query plan for a raw sql query. */ - CQueryPlan(OrmQueryRequest request, String sql, SqlTree sqlTree, String logWhereSql) { + CQueryPlan(OrmQueryRequest request, String sql, SqlTreePlan sqlTree, String logWhereSql) { this.server = request.server(); this.dataTimeZone = server.dataTimeZone(); this.beanType = request.descriptor().type(); @@ -249,7 +249,7 @@ public class CQueryPlan implements SpiQueryPlan { return rawSql ? planKey.getPartialKey() + "_" + hash : planKey.getPartialKey(); } - final SqlTree getSqlTree() { + final SqlTreePlan getSqlTree() { return sqlTree; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanRawSql.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanRawSql.java index f35702ee1..935f730ec 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanRawSql.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanRawSql.java @@ -17,7 +17,7 @@ final class CQueryPlanRawSql extends CQueryPlan { private final int[] rsetIndexPositions; CQueryPlanRawSql(OrmQueryRequest request, SqlLimitResponse sqlRes, SqlTree sqlTree, String logWhereSql) { - super(request, sqlRes, sqlTree, true, logWhereSql); + super(request, sqlRes, sqlTree.plan(), true, logWhereSql); this.rsetIndexPositions = createIndexPositions(request, sqlTree); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTree.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTree.java index 27e35c365..5b11be657 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTree.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTree.java @@ -56,6 +56,10 @@ final class SqlTree { this.noJoins = !includeJoins; } + SqlTreePlan plan() { + return new SqlTreePlan(rootNode.createLoad(), manyProperty, encryptedProps, dependentTables()); + } + /** * Return true if the query mandates SQL Distinct due to ToMany inclusion. */ @@ -123,10 +127,6 @@ final class SqlTree { return inheritanceWhereSql; } - SqlTreeRoot getRootNode() { - return (SqlTreeRoot)rootNode; - } - /** * Return the property that is associated with the many. There can only be one * per SqlSelect. This can be null. @@ -135,10 +135,6 @@ final class SqlTree { return manyProperty; } - STreeProperty[] getEncryptedProps() { - return encryptedProps; - } - /** * Return true if the query has a many join. */ @@ -153,7 +149,7 @@ final class SqlTree { /** * Return the tables that are joined in this query. */ - Set dependentTables() { + private Set dependentTables() { Set tables = new LinkedHashSet<>(); rootNode.dependentTables(tables); return tables; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoad.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoad.java new file mode 100644 index 000000000..c5ac129b1 --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoad.java @@ -0,0 +1,28 @@ +package io.ebeaninternal.server.query; + +import io.ebean.bean.EntityBean; +import io.ebean.core.type.ScalarDataReader; +import io.ebeaninternal.server.deploy.DbReadContext; + +import java.sql.SQLException; + +/** + * Tree node that loads an entity bean type. + */ +interface SqlTreeLoad { + + /** + * Load the appropriate information from the SqlSelectReader. + *

+ * At a high level this actually controls the reading of the data from the + * jdbc resultSet and putting it into the bean etc. + *

+ */ + EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean contextBean) throws SQLException; + + /** + * Return the reader for the single attribute query. + */ + ScalarDataReader getSingleAttributeReader(); + +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoadBean.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoadBean.java new file mode 100644 index 000000000..516a00537 --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoadBean.java @@ -0,0 +1,373 @@ +package io.ebeaninternal.server.query; + +import io.ebean.bean.BeanCollection; +import io.ebean.bean.EntityBean; +import io.ebean.bean.EntityBeanIntercept; +import io.ebean.bean.PersistenceContext; +import io.ebean.core.type.ScalarDataReader; +import io.ebeaninternal.api.SpiQuery; +import io.ebeaninternal.api.SpiQuery.Mode; +import io.ebeaninternal.server.deploy.DbReadContext; +import io.ebeaninternal.server.deploy.InheritInfo; +import io.ebeaninternal.server.deploy.id.IdBinder; + +import java.sql.SQLException; +import java.util.Map; + +/** + * Normal bean included in the query. + */ +class SqlTreeLoadBean implements SqlTreeLoad { + + final STreeType desc; + final IdBinder idBinder; + final SqlTreeLoad[] children; + private final boolean partialObject; + private final STreeProperty[] properties; + private final STreePropertyAssoc nodeBeanProp; + final boolean readId; + private final boolean readIdNormal; + private final boolean disableLazyLoad; + private final InheritInfo inheritInfo; + final String prefix; + private final Map pathMap; + final STreePropertyAssocMany lazyLoadParent; + private final SpiQuery.TemporalMode temporalMode; + private final boolean temporalVersions; + final IdBinder lazyLoadParentIdBinder; + + SqlTreeLoadBean(SqlTreeNodeBean node) { + this.lazyLoadParent = node.lazyLoadParent; + this.lazyLoadParentIdBinder = node.lazyLoadParentIdBinder; + this.prefix = node.prefix; + this.desc = node.desc; + this.inheritInfo = desc.inheritInfo(); + this.idBinder = desc.idBinder(); + this.temporalMode = node.temporalMode; + this.temporalVersions = node.temporalVersions; + this.nodeBeanProp = node.nodeBeanProp; + this.readId = node.readId;//!aggregationRoot && withId && desc.hasId(); + this.readIdNormal = readId && !temporalVersions; + this.disableLazyLoad = node.disableLazyLoad;// disableLazyLoad || !readIdNormal || desc.isRawSqlBased(); + this.partialObject = node.partialObject;//props.isPartialObject(); + this.properties = node.properties;//props.getProps(); + this.pathMap = node.pathMap;//createPathMap(prefix, desc); + this.children = node.createLoadChildren(); + } + + boolean isRoot() { + return false; + } + + @Override + public final ScalarDataReader getSingleAttributeReader() { + if (properties == null || properties.length == 0) { + // if we have no property ask first children (in a distinct select with join) + if (children.length == 0) { + // expected to be a findIds query + return desc.idBinder().getBeanProperty(); + } + return children[0].getSingleAttributeReader(); + } + if (properties[0] instanceof STreePropertyAssocOne) { + STreePropertyAssocOne assocOne = (STreePropertyAssocOne)properties[0]; + if (assocOne.isAssocId()) { + return assocOne.idReader(); + } + } + return properties[0]; + } + + /** + * Load that takes into account inheritance. + */ + private final class LoadInherit extends Load { + + private LoadInherit(DbReadContext ctx, EntityBean parentBean) { + super(ctx, parentBean); + } + + @Override + void initBeanType() throws SQLException { + InheritInfo localInfo = readId ? inheritInfo.readType(ctx) : desc.inheritInfo(); + if (localInfo == null) { + // the bean must be null + localIdBinder = idBinder; + localDesc = desc; + } else { + localBean = localInfo.createEntityBean(); + localType = localInfo.getType(); + localIdBinder = localInfo.getIdBinder(); + localDesc = localInfo.desc(); + } + } + + @Override + void loadProperties() { + // take account of inheritance + for (STreeProperty property : properties) { + localDesc.inheritanceLoad(sqlBeanLoad, property, ctx); + } + } + } + + /** + * Load a bean instance. + */ + class Load { + + final DbReadContext ctx; + final EntityBean parentBean; + + Object lazyLoadParentId; + Class localType; + STreeType localDesc; + IdBinder localIdBinder; + EntityBean localBean; + + Mode queryMode; + PersistenceContext persistenceContext; + Object id; + EntityBean contextBean; + SqlBeanLoad sqlBeanLoad; + boolean lazyLoadMany; + + private Load(DbReadContext ctx, EntityBean parentBean) { + this.ctx = ctx; + this.parentBean = parentBean; + } + + private void initLazyParent() throws SQLException { + if (lazyLoadParentIdBinder != null) { + lazyLoadParentId = lazyLoadParentIdBinder.read(ctx); + } + } + + void initBeanType() throws SQLException { + localDesc = desc; + localBean = desc.createEntityBean(); + localIdBinder = idBinder; + } + + private void initPersistenceContext() { + queryMode = ctx.getQueryMode(); + persistenceContext = (!readIdNormal) ? null : ctx.getPersistenceContext(); + } + + private void readId() throws SQLException { + if (readId) { + id = localIdBinder.readSet(ctx, localBean); + if (id == null) { + readIdNullBean(); + } else if (!temporalVersions) { + readIdBean(); + } + } + } + + private void readIdBean() { + // check the PersistenceContext to see if the bean already exists + contextBean = (EntityBean) localDesc.contextPutIfAbsent(persistenceContext, id, localBean); + if (contextBean == null) { + // bean just added to the persistenceContext + contextBean = localBean; + } else { + // bean already exists in persistenceContext + + if (queryMode.isLoadContextBean()) { + // if explicitly set loadContextBean to true, then reload + localBean = contextBean; + } else if (!contextBean._ebean_getIntercept().isFullyLoadedBean()) { + // reload if contextBean is partial object + localBean = contextBean; + // and switch to lazyLoad query mode in order not to overwrite + // existing properties in SqlBeanLoad::load + queryMode = Mode.LAZYLOAD_BEAN; + } else { + // ignore the DB data... + localBean = null; + } + } + } + + private void readIdNullBean() { + // bean must be null... + localBean = null; + // ... but there may exist as reference bean in parent which has to be marked as deleted. + if (parentBean != null && nodeBeanProp instanceof STreePropertyAssocOne) { + contextBean = ((STreePropertyAssocOne)nodeBeanProp).getValueAsEntityBean(parentBean); + if (contextBean != null) { + desc.markAsDeleted(contextBean); + } + } + } + + private void initSqlLoadBean() { + ctx.setCurrentPrefix(prefix, pathMap); + ctx.propagateState(localBean); + sqlBeanLoad = new SqlBeanLoad(ctx, localType, localBean, queryMode); + } + + void loadProperties() { + for (STreeProperty property : properties) { + property.load(sqlBeanLoad); + } + } + + private void loadChildren() throws SQLException { + if (localBean == null && queryMode == Mode.LAZYLOAD_MANY) { + // batch lazy load many into existing contextBean + localBean = contextBean; + lazyLoadMany = true; + } + for (SqlTreeLoad child : children) { + child.load(ctx, localBean, contextBean); + } + } + + private boolean isLazyLoadManyRoot() { + return queryMode == Mode.LAZYLOAD_MANY && isRoot(); + } + + private EntityBean getContextBean() { + return contextBean; + } + + private void postLoad() { + if (!lazyLoadMany && localBean != null) { + ctx.setCurrentPrefix(prefix, pathMap); + if (readIdNormal) { + createListProxies(); + } + if (temporalMode == SpiQuery.TemporalMode.DRAFT) { + localDesc.setDraft(localBean); + } + localDesc.postLoad(localBean); + + EntityBeanIntercept ebi = localBean._ebean_getIntercept(); + ebi.setPersistenceContext(persistenceContext); + if (Mode.LAZYLOAD_BEAN == queryMode) { + // Lazy Load does not reset the dirty state + ebi.setLoadedLazy(); + } else if (readId) { + // normal bean loading + ebi.setLoaded(); + } + + if (disableLazyLoad) { + // bean does not have an Id or is SqlSelect based + ebi.setDisableLazyLoad(true); + } else if (partialObject) { + if (readId) { + // register for lazy loading + ctx.register(null, ebi); + } + } else { + ebi.setFullyLoadedBean(true); + } + + if (ctx.isAutoTuneProfiling() && !disableLazyLoad) { + // collect autoTune profiling for this bean... + ctx.profileBean(ebi, prefix); + } + } + } + + /** + * Create lazy loading proxies for the Many's except for the one that is + * included in the actual query. + */ + private void createListProxies() { + STreePropertyAssocMany fetchedMany = ctx.getManyProperty(); + boolean forceNewReference = queryMode == Mode.REFRESH_BEAN; + for (STreePropertyAssocMany many : localDesc.propsMany()) { + if (many != fetchedMany) { + // create a proxy for the many (deferred fetching) + BeanCollection ref = many.createReference(localBean, forceNewReference); + if (ref != null) { + if (disableLazyLoad) { + ref.setDisableLazyLoad(true); + } + if (!ref.isRegisteredWithLoadContext()) { + ctx.register(many.asMany(), ref); + } + } + } + } + } + + private void setBeanToParent() { + if (parentBean != null) { + // set this back to the parentBean + nodeBeanProp.setValue(parentBean, contextBean); + } + } + + private EntityBean complete() { + if (!readIdNormal) { + // a bean with no Id (never found in context) + if (lazyLoadParentId != null) { + ctx.setLazyLoadedChildBean(localBean, lazyLoadParentId); + } + return localBean; + } else { + if (lazyLoadParentId != null) { + ctx.setLazyLoadedChildBean(contextBean, lazyLoadParentId); + } + return contextBean; + } + } + + private void initialise() throws SQLException { + initLazyParent(); + initBeanType(); + initPersistenceContext(); + readId(); + initSqlLoadBean(); + loadProperties(); + loadChildren(); + } + + /** + * Perform the load returning the loaded bean. + */ + final EntityBean perform() throws SQLException { + initialise(); + if (isLazyLoadManyRoot()) { + return getContextBean(); + } + postLoad(); + setBeanToParent(); + return complete(); + } + + /** + * Return true if this bean was already in the context. If already in the + * context we need to check if it is already contained in the collection. + */ + final boolean isContextBean() { + return localBean == null; + } + } + + /** + * read the properties from the resultSet. + */ + @Override + public EntityBean load(DbReadContext ctx, EntityBean parentBean, EntityBean contextParent) throws SQLException { + return createLoad(ctx, parentBean).perform(); + } + + /** + * Create the loader with or without inheritance. + */ + final Load createLoad(DbReadContext ctx, EntityBean parentBean) { + return (inheritInfo != null) ? new LoadInherit(ctx, parentBean) : new Load(ctx, parentBean); + } + + @Override + public String toString() { + return "SqlTreeLoadBean: " + desc; + } + +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoadManyRoot.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoadManyRoot.java new file mode 100644 index 000000000..3622e770e --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoadManyRoot.java @@ -0,0 +1,30 @@ +package io.ebeaninternal.server.query; + +import io.ebean.bean.EntityBean; +import io.ebeaninternal.server.deploy.DbReadContext; + +import java.sql.SQLException; + +final class SqlTreeLoadManyRoot extends SqlTreeLoadBean { + + private final STreePropertyAssocMany manyProp; + + SqlTreeLoadManyRoot(SqlTreeNodeManyRoot node) { + super(node); + this.manyProp = node.manyProp; + } + + @Override + public EntityBean load(DbReadContext cquery, EntityBean parentBean, EntityBean contextParent) throws SQLException { + // pass in null for parentBean because added to a collection rather than set to the parentBean + Load load = createLoad(cquery, null); + EntityBean detailBean = load.perform(); + if (contextParent != null) { + // Add to the collection and initialise collection if needed + // A null detailBean may initialise an empty collection + // Check for bean existing in collection based on load.isContextBean() + manyProp.addBeanToCollectionWithCreate(contextParent, detailBean, load.isContextBean()); + } + return detailBean; + } +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoadRoot.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoadRoot.java new file mode 100644 index 000000000..d1792ecbe --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoadRoot.java @@ -0,0 +1,43 @@ +package io.ebeaninternal.server.query; + +import io.ebean.Version; +import io.ebean.bean.EntityBean; +import io.ebeaninternal.server.deploy.DbReadContext; + +import java.sql.SQLException; +import java.sql.Timestamp; + +/** + * Represents the root node of the Sql Tree. + */ +final class SqlTreeLoadRoot extends SqlTreeLoadBean implements SqlTreeRoot { + + SqlTreeLoadRoot(SqlTreeNodeBean node) { + super(node); + } + + @Override + protected boolean isRoot() { + return true; + } + + @Override + public EntityBean load(DbReadContext ctx) throws SQLException { + return load(ctx, null, null); + } + + /** + * Read the version bean. + */ + @Override + @SuppressWarnings("unchecked") + public Version loadVersion(DbReadContext ctx) throws SQLException { + // read the sys period lower and upper bounds + // these are always the first 2 columns in the resultSet + Timestamp start = ctx.getDataReader().getTimestamp(); + Timestamp end = ctx.getDataReader().getTimestamp(); + T bean = (T) load(ctx, null, null); + return new Version<>(bean, start, end); + } + +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNode.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNode.java index 39844cb59..54632d9ca 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNode.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNode.java @@ -1,13 +1,8 @@ package io.ebeaninternal.server.query; -import io.ebean.Version; -import io.ebean.bean.EntityBean; -import io.ebean.core.type.ScalarDataReader; import io.ebeaninternal.api.SpiQuery; -import io.ebeaninternal.server.deploy.DbReadContext; import io.ebeaninternal.server.deploy.DbSqlContext; -import java.sql.SQLException; import java.util.List; import java.util.Set; @@ -64,25 +59,11 @@ interface SqlTreeNode { */ void addSoftDeletePredicate(SpiQuery query); - /** - * Load the appropriate information from the SqlSelectReader. - *

- * At a high level this actually controls the reading of the data from the - * jdbc resultSet and putting it into the bean etc. - *

- */ - EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean contextBean) throws SQLException; - /** * Return true if the query has a many join. */ boolean hasMany(); - /** - * Return the reader for the single attribute query. - */ - ScalarDataReader getSingleAttributeReader(); - /** * Return true if the query is known to only have a single property selected. */ @@ -92,4 +73,9 @@ interface SqlTreeNode { * Add dependent tables to the given set. */ void dependentTables(Set tables); + + /** + * Create the loader for this node. + */ + SqlTreeLoad createLoad(); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeBean.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeBean.java index 547eededc..4836fa3fd 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeBean.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeBean.java @@ -1,30 +1,20 @@ package io.ebeaninternal.server.query; -import io.ebean.bean.BeanCollection; -import io.ebean.bean.EntityBean; -import io.ebean.bean.EntityBeanIntercept; -import io.ebean.bean.PersistenceContext; -import io.ebean.core.type.ScalarDataReader; import io.ebean.util.SplitName; import io.ebeaninternal.api.SpiQuery; -import io.ebeaninternal.api.SpiQuery.Mode; -import io.ebeaninternal.server.deploy.DbReadContext; import io.ebeaninternal.server.deploy.DbSqlContext; import io.ebeaninternal.server.deploy.InheritInfo; import io.ebeaninternal.server.deploy.TableJoin; import io.ebeaninternal.server.deploy.id.IdBinder; -import java.sql.SQLException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; /** * Normal bean included in the query. */ class SqlTreeNodeBean implements SqlTreeNode { + private static final SqlTreeLoad[] NO_LOAD_CHILDREN = new SqlTreeLoad[0]; private static final SqlTreeNode[] NO_CHILDREN = new SqlTreeNode[0]; final STreeType desc; @@ -36,33 +26,33 @@ class SqlTreeNodeBean implements SqlTreeNode { /** * Set to true if this is a partial object fetch. */ - private final boolean partialObject; - private final STreeProperty[] properties; + final boolean partialObject; + final STreeProperty[] properties; /** * Extra where clause added by Where annotation on associated many. */ - private final String extraWhere; - private final STreePropertyAssoc nodeBeanProp; + final String extraWhere; + final STreePropertyAssoc nodeBeanProp; /** * False if report bean and has no id property. */ final boolean readId; - private final boolean readIdNormal; - private final boolean disableLazyLoad; - private final InheritInfo inheritInfo; + final boolean readIdNormal; + final boolean disableLazyLoad; + final InheritInfo inheritInfo; final String prefix; - private final Map pathMap; + final Map pathMap; final STreePropertyAssocMany lazyLoadParent; - private final SpiQuery.TemporalMode temporalMode; - private final boolean temporalVersions; - private final IdBinder lazyLoadParentIdBinder; + final SpiQuery.TemporalMode temporalMode; + final boolean temporalVersions; + final IdBinder lazyLoadParentIdBinder; String baseTableAlias; /** * Table alias set if this bean node includes a join to a intersection * table and that table has history support. */ - private boolean intersectionAsOfTableAlias; - private final boolean aggregation; + boolean intersectionAsOfTableAlias; + final boolean aggregation; /** * Construct for leaf node. @@ -108,8 +98,23 @@ class SqlTreeNodeBean implements SqlTreeNode { pathMap = createPathMap(prefix, desc); } - boolean isRoot() { - return false; + @Override + public SqlTreeLoad createLoad() { + return new SqlTreeLoadBean(this); + } + + protected SqlTreeLoad[] createLoadChildren() { + if (children.length == 0) { + return NO_LOAD_CHILDREN; + } + List loadChildren = new ArrayList<>(children.length); + for (SqlTreeNode child : children) { + SqlTreeLoad load = child.createLoad(); + if (load != null) { + loadChildren.add(load); + } + } + return loadChildren.toArray(new SqlTreeLoad[0]); } @Override @@ -117,25 +122,6 @@ class SqlTreeNodeBean implements SqlTreeNode { return properties != null && properties.length == 1 && children.length == 0; } - @Override - public final ScalarDataReader getSingleAttributeReader() { - if (properties == null || properties.length == 0) { - // if we have no property ask first children (in a distinct select with join) - if (children.length == 0) { - // expected to be a findIds query - return desc.idBinder().getBeanProperty(); - } - return children[0].getSingleAttributeReader(); - } - if (properties[0] instanceof STreePropertyAssocOne) { - STreePropertyAssocOne assocOne = (STreePropertyAssocOne)properties[0]; - if (assocOne.isAssocId()) { - return assocOne.idReader(); - } - } - return properties[0]; - } - private Map createPathMap(String prefix, STreeType desc) { HashMap m = new HashMap<>(); for (STreePropertyAssocMany many : desc.propsMany()) { @@ -173,293 +159,6 @@ class SqlTreeNodeBean implements SqlTreeNode { } } - /** - * Load that takes into account inheritance. - */ - private final class LoadInherit extends Load { - - private LoadInherit(DbReadContext ctx, EntityBean parentBean) { - super(ctx, parentBean); - } - - @Override - void initBeanType() throws SQLException { - InheritInfo localInfo = readId ? inheritInfo.readType(ctx) : desc.inheritInfo(); - if (localInfo == null) { - // the bean must be null - localIdBinder = idBinder; - localDesc = desc; - } else { - localBean = localInfo.createEntityBean(); - localType = localInfo.getType(); - localIdBinder = localInfo.getIdBinder(); - localDesc = localInfo.desc(); - } - } - - @Override - void loadProperties() { - // take account of inheritance - for (STreeProperty property : properties) { - localDesc.inheritanceLoad(sqlBeanLoad, property, ctx); - } - } - } - - /** - * Load a bean instance. - */ - class Load { - - final DbReadContext ctx; - final EntityBean parentBean; - - Object lazyLoadParentId; - Class localType; - STreeType localDesc; - IdBinder localIdBinder; - EntityBean localBean; - - Mode queryMode; - PersistenceContext persistenceContext; - Object id; - EntityBean contextBean; - SqlBeanLoad sqlBeanLoad; - boolean lazyLoadMany; - - private Load(DbReadContext ctx, EntityBean parentBean) { - this.ctx = ctx; - this.parentBean = parentBean; - } - - private void initLazyParent() throws SQLException { - if (lazyLoadParentIdBinder != null) { - lazyLoadParentId = lazyLoadParentIdBinder.read(ctx); - } - } - - void initBeanType() throws SQLException { - localDesc = desc; - localBean = desc.createEntityBean(); - localIdBinder = idBinder; - } - - private void initPersistenceContext() { - queryMode = ctx.getQueryMode(); - persistenceContext = (!readIdNormal) ? null : ctx.getPersistenceContext(); - } - - private void readId() throws SQLException { - if (readId) { - id = localIdBinder.readSet(ctx, localBean); - if (id == null) { - readIdNullBean(); - } else if (!temporalVersions) { - readIdBean(); - } - } - } - - private void readIdBean() { - // check the PersistenceContext to see if the bean already exists - contextBean = (EntityBean) localDesc.contextPutIfAbsent(persistenceContext, id, localBean); - if (contextBean == null) { - // bean just added to the persistenceContext - contextBean = localBean; - } else { - // bean already exists in persistenceContext - - if (queryMode.isLoadContextBean()) { - // if explicitly set loadContextBean to true, then reload - localBean = contextBean; - } else if (!contextBean._ebean_getIntercept().isFullyLoadedBean()) { - // reload if contextBean is partial object - localBean = contextBean; - // and switch to lazyLoad query mode in order not to overwrite - // existing properties in SqlBeanLoad::load - queryMode = Mode.LAZYLOAD_BEAN; - } else { - // ignore the DB data... - localBean = null; - } - } - } - - private void readIdNullBean() { - // bean must be null... - localBean = null; - // ... but there may exist as reference bean in parent which has to be marked as deleted. - if (parentBean != null && nodeBeanProp instanceof STreePropertyAssocOne) { - contextBean = ((STreePropertyAssocOne)nodeBeanProp).getValueAsEntityBean(parentBean); - if (contextBean != null) { - desc.markAsDeleted(contextBean); - } - } - } - - private void initSqlLoadBean() { - ctx.setCurrentPrefix(prefix, pathMap); - ctx.propagateState(localBean); - sqlBeanLoad = new SqlBeanLoad(ctx, localType, localBean, queryMode); - } - - void loadProperties() { - for (STreeProperty property : properties) { - property.load(sqlBeanLoad); - } - } - - private void loadChildren() throws SQLException { - if (localBean == null && queryMode == Mode.LAZYLOAD_MANY) { - // batch lazy load many into existing contextBean - localBean = contextBean; - lazyLoadMany = true; - } - for (SqlTreeNode child : children) { - child.load(ctx, localBean, contextBean); - } - } - - private boolean isLazyLoadManyRoot() { - return queryMode == Mode.LAZYLOAD_MANY && isRoot(); - } - - private EntityBean getContextBean() { - return contextBean; - } - - private void postLoad() { - if (!lazyLoadMany && localBean != null) { - ctx.setCurrentPrefix(prefix, pathMap); - if (readIdNormal) { - createListProxies(); - } - if (temporalMode == SpiQuery.TemporalMode.DRAFT) { - localDesc.setDraft(localBean); - } - localDesc.postLoad(localBean); - - EntityBeanIntercept ebi = localBean._ebean_getIntercept(); - ebi.setPersistenceContext(persistenceContext); - if (Mode.LAZYLOAD_BEAN == queryMode) { - // Lazy Load does not reset the dirty state - ebi.setLoadedLazy(); - } else if (readId) { - // normal bean loading - ebi.setLoaded(); - } - - if (disableLazyLoad) { - // bean does not have an Id or is SqlSelect based - ebi.setDisableLazyLoad(true); - } else if (partialObject) { - if (readId) { - // register for lazy loading - ctx.register(null, ebi); - } - } else { - ebi.setFullyLoadedBean(true); - } - - if (ctx.isAutoTuneProfiling() && !disableLazyLoad) { - // collect autoTune profiling for this bean... - ctx.profileBean(ebi, prefix); - } - } - } - - /** - * Create lazy loading proxies for the Many's except for the one that is - * included in the actual query. - */ - private void createListProxies() { - STreePropertyAssocMany fetchedMany = ctx.getManyProperty(); - boolean forceNewReference = queryMode == Mode.REFRESH_BEAN; - for (STreePropertyAssocMany many : localDesc.propsMany()) { - if (many != fetchedMany) { - // create a proxy for the many (deferred fetching) - BeanCollection ref = many.createReference(localBean, forceNewReference); - if (ref != null) { - if (disableLazyLoad) { - ref.setDisableLazyLoad(true); - } - if (!ref.isRegisteredWithLoadContext()) { - ctx.register(many.asMany(), ref); - } - } - } - } - } - - private void setBeanToParent() { - if (parentBean != null) { - // set this back to the parentBean - nodeBeanProp.setValue(parentBean, contextBean); - } - } - - private EntityBean complete() { - if (!readIdNormal) { - // a bean with no Id (never found in context) - if (lazyLoadParentId != null) { - ctx.setLazyLoadedChildBean(localBean, lazyLoadParentId); - } - return localBean; - } else { - if (lazyLoadParentId != null) { - ctx.setLazyLoadedChildBean(contextBean, lazyLoadParentId); - } - return contextBean; - } - } - - private void initialise() throws SQLException { - initLazyParent(); - initBeanType(); - initPersistenceContext(); - readId(); - initSqlLoadBean(); - loadProperties(); - loadChildren(); - } - - /** - * Perform the load returning the loaded bean. - */ - final EntityBean perform() throws SQLException { - initialise(); - if (isLazyLoadManyRoot()) { - return getContextBean(); - } - postLoad(); - setBeanToParent(); - return complete(); - } - - /** - * Return true if this bean was already in the context. If already in the - * context we need to check if it is already contained in the collection. - */ - final boolean isContextBean() { - return localBean == null; - } - } - - /** - * read the properties from the resultSet. - */ - @Override - public EntityBean load(DbReadContext ctx, EntityBean parentBean, EntityBean contextParent) throws SQLException { - return createLoad(ctx, parentBean).perform(); - } - - /** - * Create the loader with or without inheritance. - */ - final Load createLoad(DbReadContext ctx, EntityBean parentBean) { - return (inheritInfo != null) ? new LoadInherit(ctx, parentBean) : new Load(ctx, parentBean); - } - @Override public final void appendGroupBy(DbSqlContext ctx, boolean subQuery) { ctx.pushJoin(prefix); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java index e7ca5d9ab..42d2395b5 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java @@ -38,6 +38,11 @@ final class SqlTreeNodeExtraJoin implements SqlTreeNode { this.manyJoin = assocBeanProperty instanceof STreePropertyAssocMany; } + @Override + public SqlTreeLoad createLoad() { + return null; + } + @Override public boolean isSingleProperty() { return false; @@ -73,11 +78,6 @@ final class SqlTreeNodeExtraJoin implements SqlTreeNode { // nothing to do here } - @Override - public ScalarType getSingleAttributeReader() { - throw new IllegalStateException("No expected"); - } - /** * Return true if the extra join is a many join. *

@@ -184,14 +184,6 @@ final class SqlTreeNodeExtraJoin implements SqlTreeNode { public void appendWhere(DbSqlContext ctx) { } - /** - * Does nothing. - */ - @Override - public EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean parentBean) { - return null; - } - @Override public boolean hasMany() { return manyJoin; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeFormulaWhereJoin.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeFormulaWhereJoin.java index 06a71133a..502c28a6d 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeFormulaWhereJoin.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeFormulaWhereJoin.java @@ -33,13 +33,13 @@ final class SqlTreeNodeFormulaWhereJoin implements SqlTreeNode { } @Override - public boolean isSingleProperty() { - return true; + public SqlTreeLoad createLoad() { + return null; } @Override - public ScalarType getSingleAttributeReader() { - throw new IllegalStateException("No expected"); + public boolean isSingleProperty() { + return true; } @Override @@ -98,12 +98,6 @@ final class SqlTreeNodeFormulaWhereJoin implements SqlTreeNode { // nothing to do here } - @Override - public EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean parentBean) { - // nothing to do here - return null; - } - @Override public boolean hasMany() { return true; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java index c030e96bc..a8b323533 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java @@ -1,16 +1,13 @@ package io.ebeaninternal.server.query; -import io.ebean.bean.EntityBean; import io.ebeaninternal.api.SpiQuery; -import io.ebeaninternal.server.deploy.DbReadContext; import io.ebeaninternal.server.deploy.DbSqlContext; -import java.sql.SQLException; import java.util.List; final class SqlTreeNodeManyRoot extends SqlTreeNodeBean { - private final STreePropertyAssocMany manyProp; + final STreePropertyAssocMany manyProp; SqlTreeNodeManyRoot(String prefix, STreePropertyAssocMany prop, SqlTreeProperties props, List myList, boolean withId, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) { @@ -19,22 +16,13 @@ final class SqlTreeNodeManyRoot extends SqlTreeNodeBean { } @Override - public boolean hasMany() { - return true; + public SqlTreeLoad createLoad() { + return new SqlTreeLoadManyRoot(this); } @Override - public EntityBean load(DbReadContext cquery, EntityBean parentBean, EntityBean contextParent) throws SQLException { - // pass in null for parentBean because added to a collection rather than set to the parentBean - SqlTreeNodeBean.Load load = createLoad(cquery, null); - EntityBean detailBean = load.perform(); - if (contextParent != null) { - // Add to the collection and initialise collection if needed - // A null detailBean may initialise an empty collection - // Check for bean existing in collection based on load.isContextBean() - manyProp.addBeanToCollectionWithCreate(contextParent, detailBean, load.isContextBean()); - } - return detailBean; + public boolean hasMany() { + return true; } /** diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java index 73c7a18b2..cb2ba54e7 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java @@ -1,10 +1,7 @@ package io.ebeaninternal.server.query; -import io.ebean.bean.EntityBean; -import io.ebean.core.type.ScalarType; import io.ebean.util.SplitName; import io.ebeaninternal.api.SpiQuery; -import io.ebeaninternal.server.deploy.DbReadContext; import io.ebeaninternal.server.deploy.DbSqlContext; import io.ebeaninternal.server.deploy.TableJoin; @@ -37,13 +34,13 @@ final class SqlTreeNodeManyWhereJoin implements SqlTreeNode { } @Override - public boolean isSingleProperty() { - return false; + public SqlTreeLoad createLoad() { + return null; } @Override - public ScalarType getSingleAttributeReader() { - throw new IllegalStateException("No expected"); + public boolean isSingleProperty() { + return false; } @Override @@ -132,12 +129,6 @@ final class SqlTreeNodeManyWhereJoin implements SqlTreeNode { // nothing to do here } - @Override - public EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean parentBean) { - // nothing to do here - return null; - } - @Override public boolean hasMany() { return true; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeRoot.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeRoot.java index a58c51cc5..c25024624 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeRoot.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeRoot.java @@ -1,21 +1,16 @@ package io.ebeaninternal.server.query; -import io.ebean.Version; -import io.ebean.bean.EntityBean; import io.ebeaninternal.api.SpiQuery; -import io.ebeaninternal.server.deploy.DbReadContext; import io.ebeaninternal.server.deploy.DbSqlContext; import io.ebeaninternal.server.deploy.TableJoin; -import java.sql.SQLException; -import java.sql.Timestamp; import java.util.List; import java.util.Set; /** * Represents the root node of the Sql Tree. */ -final class SqlTreeNodeRoot extends SqlTreeNodeBean implements SqlTreeRoot { +final class SqlTreeNodeRoot extends SqlTreeNodeBean { private final TableJoin includeJoin; private final boolean sqlDistinct; @@ -34,27 +29,8 @@ final class SqlTreeNodeRoot extends SqlTreeNodeBean implements SqlTreeRoot { } @Override - protected boolean isRoot() { - return true; - } - - @Override - public EntityBean load(DbReadContext ctx) throws SQLException { - return load(ctx, null, null); - } - - /** - * Read the version bean. - */ - @Override - @SuppressWarnings("unchecked") - public Version loadVersion(DbReadContext ctx) throws SQLException { - // read the sys period lower and upper bounds - // these are always the first 2 columns in the resultSet - Timestamp start = ctx.getDataReader().getTimestamp(); - Timestamp end = ctx.getDataReader().getTimestamp(); - T bean = (T) load(ctx, null, null); - return new Version<>(bean, start, end); + public SqlTreeLoad createLoad() { + return new SqlTreeLoadRoot(this); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreePlan.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreePlan.java new file mode 100644 index 000000000..7a0e929ef --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreePlan.java @@ -0,0 +1,41 @@ +package io.ebeaninternal.server.query; + +import java.util.Set; + +final class SqlTreePlan { + + private final SqlTreeLoad rootNode; + private final STreePropertyAssocMany manyProperty; + private final STreeProperty[] encryptedProps; + private final Set dependentTables; + + SqlTreePlan(SqlTreeLoad rootNode, STreePropertyAssocMany manyProperty, STreeProperty[] encryptedProps, Set dependentTables) { + this.rootNode = rootNode; + this.manyProperty = manyProperty; + this.encryptedProps = encryptedProps; + this.dependentTables = dependentTables; + } + + SqlTreeRoot getRootNode() { + return (SqlTreeRoot)rootNode; + } + + /** + * Return the property that is associated with the many. There can only be one + * per SqlSelect. This can be null. + */ + STreePropertyAssocMany getManyProperty() { + return manyProperty; + } + + STreeProperty[] getEncryptedProps() { + return encryptedProps; + } + + /** + * Return the tables that are joined in this query. + */ + Set dependentTables() { + return dependentTables; + } +}