From 74a9019535d0f0355babaac15fb42f7dfc810745 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Tue, 20 Mar 2018 14:22:29 +1300 Subject: [PATCH] #115 - Mapping - Add support for @ElementCollection enhancement Initial support - simple List. --- .../io/ebeaninternal/api/LoadManyRequest.java | 5 +- .../model/build/ModelBuildContext.java | 50 ++++ .../model/build/ModelBuildElementTable.java | 34 +++ .../build/ModelBuildIntersectionTable.java | 33 +-- .../build/ModelBuildPropertyVisitor.java | 33 +-- .../service/ProfileOriginNodeUsage.java | 4 +- .../server/core/OrmQueryRequest.java | 15 +- .../server/deploy/BaseCollectionHelp.java | 50 ++++ .../server/deploy/BeanCollectionHelp.java | 3 +- .../deploy/BeanCollectionHelpFactory.java | 8 +- .../server/deploy/BeanDescriptor.java | 137 +++++++--- .../server/deploy/BeanDescriptorElement.java | 24 ++ .../deploy/BeanDescriptorInitContext.java | 32 +++ .../server/deploy/BeanDescriptorManager.java | 80 ++++-- .../server/deploy/BeanListHelp.java | 47 +--- .../server/deploy/BeanListHelpElement.java | 37 +++ .../server/deploy/BeanMapHelp.java | 11 +- .../server/deploy/BeanProperty.java | 32 ++- .../server/deploy/BeanPropertyAssoc.java | 73 ++++- .../server/deploy/BeanPropertyAssocMany.java | 251 ++++------------- .../deploy/BeanPropertyAssocManyJsonHelp.java | 4 +- .../BeanPropertyAssocManyJsonTransient.java | 4 +- .../deploy/BeanPropertyAssocManySqlHelp.java | 258 ++++++++++++++++++ .../server/deploy/BeanPropertyAssocOne.java | 36 +-- .../deploy/BeanPropertySimpleCollection.java | 19 ++ .../server/deploy/BeanSetHelp.java | 41 +-- .../server/deploy/BeanTable.java | 10 + .../server/deploy/DbReadContext.java | 3 +- .../server/deploy/DynamicPropertyBase.java | 16 +- .../server/deploy/ElementEntityBean.java | 81 ++++++ .../server/deploy/FormulaPropertyPath.java | 4 +- .../server/deploy/InheritInfo.java | 2 +- .../server/deploy/id/IdBinder.java | 3 +- .../meta/BeanPropertyElementSetter.java | 26 ++ .../deploy/meta/DeployBeanProperty.java | 14 +- .../meta/DeployBeanPropertyAssocMany.java | 27 ++ .../meta/DeployBeanPropertyAssocOne.java | 9 + .../deploy/parse/AnnotationAssocManys.java | 85 +++++- .../deploy/parse/AnnotationAssocOnes.java | 6 +- .../server/deploy/parse/AnnotationClass.java | 20 +- .../server/deploy/parse/AnnotationFields.java | 13 +- .../server/deploy/parse/AnnotationParser.java | 7 +- .../server/deploy/parse/AnnotationSql.java | 6 +- .../server/deploy/parse/DeployBeanInfo.java | 10 - .../deploy/parse/ReadAnnotationConfig.java | 58 ++++ .../server/deploy/parse/ReadAnnotations.java | 51 +--- .../deploy/parse/TransientProperties.java | 25 +- .../server/loadcontext/DLoadContext.java | 2 +- .../server/loadcontext/DLoadManyContext.java | 2 +- .../server/persist/DefaultPersister.java | 24 +- .../server/persist/SaveManyPropRequest.java | 14 +- .../persist/SaveManySimpleCollection.java | 39 +++ .../io/ebeaninternal/server/query/CQuery.java | 60 ++-- .../server/query/CQueryBuilder.java | 10 +- .../server/query/CQueryCollectionAdd.java | 21 ++ .../server/query/CQueryCollectionAddNoop.java | 27 ++ .../server/query/CQueryPlan.java | 8 +- .../server/query/CQueryPredicates.java | 2 +- .../ebeaninternal/server/query/ExtraJoin.java | 20 ++ ...qlTreeProperty.java => STreeProperty.java} | 14 +- .../server/query/STreePropertyAssoc.java | 39 +++ .../server/query/STreePropertyAssocMany.java | 45 +++ .../server/query/STreePropertyAssocOne.java | 16 ++ .../ebeaninternal/server/query/STreeType.java | 136 +++++++++ .../ebeaninternal/server/query/SqlTree.java | 12 +- .../server/query/SqlTreeAlias.java | 16 +- .../server/query/SqlTreeBuilder.java | 102 +++---- .../server/query/SqlTreeNodeBean.java | 83 +++--- .../server/query/SqlTreeNodeExtraJoin.java | 19 +- .../server/query/SqlTreeNodeManyRoot.java | 5 +- .../query/SqlTreeNodeManyWhereJoin.java | 19 +- .../server/query/SqlTreeNodeRoot.java | 6 +- .../server/query/SqlTreeProperties.java | 12 +- .../server/querydefn/OrmQueryDetail.java | 4 +- .../docstore/api/support/DocStructure.java | 2 +- .../deploy/FormulaPropertyPathTest.java | 4 +- .../org/tests/basic/TestAnnotationBase.java | 16 +- .../model/elementcollection/EcPerson.java | 69 +++++ .../TestElementCollectionBasic.java | 51 ++++ 79 files changed, 1891 insertions(+), 805 deletions(-) create mode 100644 src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildElementTable.java create mode 100644 src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorElement.java create mode 100644 src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorInitContext.java create mode 100644 src/main/java/io/ebeaninternal/server/deploy/BeanListHelpElement.java create mode 100644 src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManySqlHelp.java create mode 100644 src/main/java/io/ebeaninternal/server/deploy/ElementEntityBean.java create mode 100644 src/main/java/io/ebeaninternal/server/deploy/meta/BeanPropertyElementSetter.java create mode 100644 src/main/java/io/ebeaninternal/server/deploy/parse/ReadAnnotationConfig.java create mode 100644 src/main/java/io/ebeaninternal/server/persist/SaveManySimpleCollection.java create mode 100644 src/main/java/io/ebeaninternal/server/query/CQueryCollectionAdd.java create mode 100644 src/main/java/io/ebeaninternal/server/query/CQueryCollectionAddNoop.java create mode 100644 src/main/java/io/ebeaninternal/server/query/ExtraJoin.java rename src/main/java/io/ebeaninternal/server/query/{SqlTreeProperty.java => STreeProperty.java} (85%) create mode 100644 src/main/java/io/ebeaninternal/server/query/STreePropertyAssoc.java create mode 100644 src/main/java/io/ebeaninternal/server/query/STreePropertyAssocMany.java create mode 100644 src/main/java/io/ebeaninternal/server/query/STreePropertyAssocOne.java create mode 100644 src/main/java/io/ebeaninternal/server/query/STreeType.java create mode 100644 src/test/java/org/tests/model/elementcollection/EcPerson.java create mode 100644 src/test/java/org/tests/model/elementcollection/TestElementCollectionBasic.java diff --git a/src/main/java/io/ebeaninternal/api/LoadManyRequest.java b/src/main/java/io/ebeaninternal/api/LoadManyRequest.java index d92ce3222..1b28ed2c3 100644 --- a/src/main/java/io/ebeaninternal/api/LoadManyRequest.java +++ b/src/main/java/io/ebeaninternal/api/LoadManyRequest.java @@ -1,6 +1,5 @@ package io.ebeaninternal.api; -import io.ebean.EbeanServer; import io.ebean.bean.BeanCollection; import io.ebean.bean.EntityBean; import io.ebean.util.StringHelper; @@ -124,11 +123,11 @@ public class LoadManyRequest extends LoadRequest { return loadContext.getBeanProperty(); } - public SpiQuery createQuery(EbeanServer server, int batchSize) { + public SpiQuery createQuery(SpiEbeanServer server, int batchSize) { BeanPropertyAssocMany many = getMany(); - SpiQuery query = (SpiQuery) server.createQuery(many.getTargetType()); + SpiQuery query = many.newQuery(server); String orderBy = many.getLazyFetchOrderBy(); if (orderBy != null) { query.orderBy(orderBy); diff --git a/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildContext.java b/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildContext.java index c897617a9..9fc213faa 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildContext.java +++ b/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildContext.java @@ -4,13 +4,18 @@ import io.ebean.config.DbConstraintNaming; import io.ebean.config.dbplatform.DbPlatformType; import io.ebean.config.dbplatform.DbPlatformTypeMapping; import io.ebeaninternal.dbmigration.model.MColumn; +import io.ebeaninternal.dbmigration.model.MCompoundForeignKey; import io.ebeaninternal.dbmigration.model.MTable; import io.ebeaninternal.dbmigration.model.ModelContainer; +import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeaninternal.server.deploy.BeanProperty; +import io.ebeaninternal.server.deploy.TableJoin; +import io.ebeaninternal.server.deploy.TableJoinColumn; import io.ebeaninternal.server.type.ScalarType; import java.util.Collection; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; /** * The context used during DDL generation. @@ -187,4 +192,49 @@ public class ModelBuildContext { addTable(draftTable); } + /** + * Return a builder to add foreign keys. + */ + public FkeyBuilder fkeyBuilder(MTable destTable) { + return new FkeyBuilder(this, destTable); + } + + public static class FkeyBuilder { + + private final AtomicInteger count = new AtomicInteger(); + + private final ModelBuildContext ctx; + + private final MTable destTable; + + private final String tableName; + + FkeyBuilder(ModelBuildContext ctx, MTable destTable) { + this.ctx = ctx; + this.destTable = destTable; + this.tableName = destTable.getName(); + } + + /** + * Add a foreign key based on the table join. + */ + public FkeyBuilder addForeignKey(BeanDescriptor desc, TableJoin tableJoin, boolean direction) { + + String baseTable = ctx.normaliseTable(desc.getBaseTable()); + String fkName = ctx.foreignKeyConstraintName(tableName, baseTable, count.incrementAndGet()); + String fkIndex = ctx.foreignKeyIndexName(tableName, baseTable, count.get()); + + MCompoundForeignKey foreignKey = new MCompoundForeignKey(fkName, desc.getBaseTable(), fkIndex); + + for (TableJoinColumn column : tableJoin.columns()) { + String localCol = direction ? column.getForeignDbColumn() : column.getLocalDbColumn(); + String refCol = !direction ? column.getForeignDbColumn() : column.getLocalDbColumn(); + foreignKey.addColumnPair(localCol, refCol); + } + + destTable.addForeignKey(foreignKey); + return this; + } + + } } diff --git a/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildElementTable.java b/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildElementTable.java new file mode 100644 index 000000000..94a2526a8 --- /dev/null +++ b/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildElementTable.java @@ -0,0 +1,34 @@ +package io.ebeaninternal.dbmigration.model.build; + +import io.ebeaninternal.dbmigration.model.MTable; +import io.ebeaninternal.dbmigration.model.visitor.VisitAllUsing; +import io.ebeaninternal.server.deploy.BeanDescriptor; +import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; +import io.ebeaninternal.server.deploy.BeanTable; + +/** + * Add the element collection table to the model. + */ +public class ModelBuildElementTable { + + /** + * Build and add the MTable model for the ElementCollection property. + */ + public static void build(ModelBuildContext ctx, BeanPropertyAssocMany manyProp) { + + BeanTable beanTable = manyProp.getBeanTable(); + + BeanDescriptor targetDescriptor = manyProp.getTargetDescriptor(); + + MTable table = new MTable(beanTable.getBaseTable()); + + VisitAllUsing.visitOne(targetDescriptor, new ModelBuildPropertyVisitor(ctx, table, targetDescriptor)); + + ctx.fkeyBuilder(table) + .addForeignKey(manyProp.getBeanDescriptor(), manyProp.getTableJoin(), true); + + ctx.addTable(table); + } + + +} diff --git a/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildIntersectionTable.java b/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildIntersectionTable.java index 9bf4d50c0..6772e57d6 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildIntersectionTable.java +++ b/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildIntersectionTable.java @@ -1,7 +1,6 @@ package io.ebeaninternal.dbmigration.model.build; import io.ebeaninternal.dbmigration.model.MColumn; -import io.ebeaninternal.dbmigration.model.MCompoundForeignKey; import io.ebeaninternal.dbmigration.model.MTable; import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeaninternal.server.deploy.BeanProperty; @@ -12,7 +11,7 @@ import io.ebeaninternal.server.deploy.TableJoinColumn; /** * Add the intersection table to the model. */ -public class ModelBuildIntersectionTable { +class ModelBuildIntersectionTable { private final ModelBuildContext ctx; @@ -22,9 +21,7 @@ public class ModelBuildIntersectionTable { private MTable intersectionTable; - private int countForeignKey; - - public ModelBuildIntersectionTable(ModelBuildContext ctx, BeanPropertyAssocMany manyProp) { + ModelBuildIntersectionTable(ModelBuildContext ctx, BeanPropertyAssocMany manyProp) { this.ctx = ctx; this.manyProp = manyProp; this.intersectionTableJoin = manyProp.getIntersectionTableJoin(); @@ -51,33 +48,13 @@ public class ModelBuildIntersectionTable { private void buildFkConstraints() { - BeanDescriptor localDesc = manyProp.getBeanDescriptor(); - buildFkConstraints(localDesc, intersectionTableJoin.columns(), true); - - BeanDescriptor targetDesc = manyProp.getTargetDescriptor(); - buildFkConstraints(targetDesc, tableJoin.columns(), false); + ctx.fkeyBuilder(intersectionTable) + .addForeignKey(manyProp.getBeanDescriptor(), intersectionTableJoin, true) + .addForeignKey(manyProp.getTargetDescriptor(), tableJoin, false); intersectionTable.checkDuplicateForeignKeys(); } - - private void buildFkConstraints(BeanDescriptor desc, TableJoinColumn[] columns, boolean direction) { - - String tableName = intersectionTableJoin.getTable(); - String baseTable = ctx.normaliseTable(desc.getBaseTable()); - String fkName = ctx.foreignKeyConstraintName(tableName, baseTable, ++countForeignKey); - String fkIndex = ctx.foreignKeyIndexName(tableName, baseTable, countForeignKey); - - MCompoundForeignKey foreignKey = new MCompoundForeignKey(fkName, desc.getBaseTable(), fkIndex); - intersectionTable.addForeignKey(foreignKey); - - for (TableJoinColumn column : columns) { - String localCol = direction ? column.getForeignDbColumn() : column.getLocalDbColumn(); - String refCol = !direction ? column.getForeignDbColumn() : column.getLocalDbColumn(); - foreignKey.addColumnPair(localCol, refCol); - } - } - private MTable createTable() { BeanDescriptor localDesc = manyProp.getBeanDescriptor(); diff --git a/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java b/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java index dc9bc4692..c1ad11197 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java +++ b/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java @@ -123,27 +123,24 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor { @Override public void visitMany(BeanPropertyAssocMany p) { - if (p.hasJoinTable()) { - if (p.getMappedBy() == null) { - // only create on other 'owning' side + if (p.hasJoinTable() && p.getMappedBy() == null) { + // only create on other 'owning' side - //TableJoin intersectionTableJoin = p.getIntersectionTableJoin(); - // check if the intersection table has already been created - - // build the create table and fkey constraints - // putting the DDL into ctx for later output as we are - // in the middle of rendering the create table DDL - MTable intersectionTable = new ModelBuildIntersectionTable(ctx, p).build(); - if (p.isO2mJoinTable()) { - intersectionTable.clearForeignKeyIndexes(); - Collection cols = intersectionTable.allColumns(); - if (cols.size() == 2) { - // always the second column that we put the unique constraint on - MColumn col = new ArrayList<>(cols).get(1); - col.setUnique(determineUniqueConstraintName(col.getName())); - } + // build the create table and fkey constraints + // putting the DDL into ctx for later output as we are + // in the middle of rendering the create table DDL + MTable intersectionTable = new ModelBuildIntersectionTable(ctx, p).build(); + if (p.isO2mJoinTable()) { + intersectionTable.clearForeignKeyIndexes(); + Collection cols = intersectionTable.allColumns(); + if (cols.size() == 2) { + // always the second column that we put the unique constraint on + MColumn col = new ArrayList<>(cols).get(1); + col.setUnique(determineUniqueConstraintName(col.getName())); } } + } else if (p.isElementCollection()) { + ModelBuildElementTable.build(ctx, p); } } diff --git a/src/main/java/io/ebeaninternal/server/autotune/service/ProfileOriginNodeUsage.java b/src/main/java/io/ebeaninternal/server/autotune/service/ProfileOriginNodeUsage.java index 391db0366..9b9ebc50c 100644 --- a/src/main/java/io/ebeaninternal/server/autotune/service/ProfileOriginNodeUsage.java +++ b/src/main/java/io/ebeaninternal/server/autotune/service/ProfileOriginNodeUsage.java @@ -2,11 +2,11 @@ package io.ebeaninternal.server.autotune.service; import io.ebean.bean.NodeUsageCollector; import io.ebean.text.PathProperties; +import io.ebean.util.SplitName; import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeaninternal.server.deploy.BeanProperty; import io.ebeaninternal.server.deploy.BeanPropertyAssoc; import io.ebeaninternal.server.el.ElPropertyValue; -import io.ebean.util.SplitName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,7 +56,7 @@ public class ProfileOriginNodeUsage { } for (String propName : aggregateUsed) { - BeanProperty beanProp = desc.getBeanPropertyFromPath(propName); + BeanProperty beanProp = desc.findPropertyFromPath(propName); if (beanProp == null) { logger.warn("AutoTune: Can't find property[" + propName + "] for " + desc.getName()); diff --git a/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java b/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java index 5a9e3f6a8..ed09fdb20 100644 --- a/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java +++ b/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java @@ -79,6 +79,8 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery private List cacheBeans; + private BeanPropertyAssocMany manyProperty; + /** * Create the InternalQueryRequest. */ @@ -496,11 +498,18 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery } /** - * Return the many property that is fetched in the query or null if there is - * not one. + * Determine and return the ToMany property that is included in the query. + */ + public BeanPropertyAssocMany determineMany() { + manyProperty = beanDescriptor.getManyProperty(query); + return manyProperty; + } + + /** + * Return the many property that is fetched in the query or null if there is not one. */ public BeanPropertyAssocMany getManyProperty() { - return beanDescriptor.getManyProperty(query); + return manyProperty; } /** diff --git a/src/main/java/io/ebeaninternal/server/deploy/BaseCollectionHelp.java b/src/main/java/io/ebeaninternal/server/deploy/BaseCollectionHelp.java index 5e50e9700..4d8dbda97 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BaseCollectionHelp.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BaseCollectionHelp.java @@ -1,11 +1,47 @@ package io.ebeaninternal.server.deploy; import io.ebean.bean.BeanCollection; +import io.ebean.bean.BeanCollectionLoader; +import io.ebean.bean.EntityBean; +import io.ebeaninternal.server.text.json.SpiJsonWriter; +import java.io.IOException; import java.util.Collection; abstract class BaseCollectionHelp implements BeanCollectionHelp { + final BeanPropertyAssocMany many; + final BeanDescriptor targetDescriptor; + final String propertyName; + + BeanCollectionLoader loader; + + BaseCollectionHelp(BeanPropertyAssocMany many) { + this.many = many; + this.targetDescriptor = many.getTargetDescriptor(); + this.propertyName = many.getName(); + } + + BaseCollectionHelp() { + this.many = null; + this.targetDescriptor = null; + this.propertyName = null; + } + + @Override + public void setLoader(BeanCollectionLoader loader) { + this.loader = loader; + } + + @Override + public void add(BeanCollection collection, EntityBean bean, boolean withCheck) { + if (withCheck) { + collection.internalAddWithCheck(bean); + } else { + collection.internalAdd(bean); + } + } + @Override public Collection underlying(Object value) { if (value instanceof BeanCollection) { @@ -14,4 +50,18 @@ abstract class BaseCollectionHelp implements BeanCollectionHelp { return (Collection)value; } } + + void jsonWriteCollection(SpiJsonWriter ctx, String name, Collection list) throws IOException { + if (!list.isEmpty() || ctx.isIncludeEmpty()) { + ctx.beginAssocMany(name); + for (Object bean : list) { + jsonWriteElement(ctx, bean); + } + ctx.endAssocMany(); + } + } + + void jsonWriteElement(SpiJsonWriter ctx, Object bean) throws IOException { + targetDescriptor.jsonWrite(ctx, (EntityBean) bean); + } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionHelp.java b/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionHelp.java index 8891048bb..569e22403 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionHelp.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionHelp.java @@ -7,6 +7,7 @@ import io.ebean.bean.BeanCollection; import io.ebean.bean.BeanCollectionAdd; import io.ebean.bean.BeanCollectionLoader; import io.ebean.bean.EntityBean; +import io.ebeaninternal.server.query.CQueryCollectionAdd; import io.ebeaninternal.server.text.json.SpiJsonWriter; import java.io.IOException; @@ -15,7 +16,7 @@ import java.util.Collection; /** * Helper functions for performing tasks on Lists Sets or Maps. */ -public interface BeanCollectionHelp { +public interface BeanCollectionHelp extends CQueryCollectionAdd { /** * Set the EbeanServer that owns the configuration. diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionHelpFactory.java b/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionHelpFactory.java index 2ba289d75..f74a67243 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionHelpFactory.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanCollectionHelpFactory.java @@ -20,10 +20,12 @@ public class BeanCollectionHelpFactory { */ public static BeanCollectionHelp create(BeanPropertyAssocMany manyProperty) { + boolean elementCollection = manyProperty.isElementCollection(); + ManyType manyType = manyProperty.getManyType(); switch (manyType) { case LIST: - return new BeanListHelp<>(manyProperty); + return elementCollection ? new BeanListHelpElement<>(manyProperty) : new BeanListHelp<>(manyProperty); case SET: return new BeanSetHelp<>(manyProperty); case MAP: @@ -35,9 +37,7 @@ public class BeanCollectionHelpFactory { } @SuppressWarnings("unchecked") - public static BeanCollectionHelp create(OrmQueryRequest request) { - - SpiQuery.Type manyType = request.getQuery().getType(); + public static BeanCollectionHelp create(SpiQuery.Type manyType, OrmQueryRequest request) { if (manyType == SpiQuery.Type.LIST) { return LIST_HELP; diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index 8ba1b5ae6..a7614cbba 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -62,7 +62,13 @@ import io.ebeaninternal.server.el.ElPropertyDeploy; import io.ebeaninternal.server.el.ElPropertyValue; import io.ebeaninternal.server.persist.DmlUtil; import io.ebeaninternal.server.query.CQueryPlan; -import io.ebeaninternal.server.query.SqlTreeProperty; +import io.ebeaninternal.server.query.ExtraJoin; +import io.ebeaninternal.server.query.STreeProperty; +import io.ebeaninternal.server.query.STreePropertyAssoc; +import io.ebeaninternal.server.query.STreePropertyAssocMany; +import io.ebeaninternal.server.query.STreePropertyAssocOne; +import io.ebeaninternal.server.query.STreeType; +import io.ebeaninternal.server.query.SqlBeanLoad; import io.ebeaninternal.server.querydefn.OrmQueryDetail; import io.ebeaninternal.server.rawsql.SpiRawSql; import io.ebeaninternal.server.text.json.ReadJson; @@ -100,7 +106,7 @@ import java.util.concurrent.ConcurrentHashMap; /** * Describes Beans including their deployment information. */ -public class BeanDescriptor implements BeanType { +public class BeanDescriptor implements BeanType, STreeType { private static final Logger logger = LoggerFactory.getLogger(BeanDescriptor.class); @@ -114,7 +120,7 @@ public class BeanDescriptor implements BeanType { private final ConcurrentHashMap> comparatorCache = new ConcurrentHashMap<>(); - private final ConcurrentHashMap dynamicProperty = new ConcurrentHashMap<>(); + private final ConcurrentHashMap dynamicProperty = new ConcurrentHashMap<>(); private final Map namedRawSql; @@ -239,7 +245,7 @@ public class BeanDescriptor implements BeanType { private final BeanDescriptorMap owner; - private final String[] properties; + final String[] properties; /** * Intercept pre post on insert,update, and delete . @@ -601,7 +607,7 @@ public class BeanDescriptor implements BeanType { /** * Create an entity bean that is used as a prototype/factory to create new instances. */ - private EntityBean createPrototypeEntityBean(Class beanType) { + protected EntityBean createPrototypeEntityBean(Class beanType) { if (Modifier.isAbstract(beanType.getModifiers())) { return null; } @@ -662,21 +668,19 @@ public class BeanDescriptor implements BeanType { * These properties need to be initialised prior to the association properties * as they are used to get the imported and exported properties. *

- * - * @param withHistoryTables map populated if @History is supported on this entity bean */ - public void initialiseId(Map withHistoryTables, Map draftTables) { + public void initialiseId(BeanDescriptorInitContext initContext) { if (logger.isTraceEnabled()) { logger.trace("BeanDescriptor initialise " + fullName); } if (draftable) { - draftTables.put(baseTable, draftTable); + initContext.addDraft(baseTable, draftTable); } if (historySupport) { // add mapping (used to swap out baseTable for asOf queries) - withHistoryTables.put(baseTable, baseTableAsOf); + initContext.addHistory(baseTable, baseTableAsOf); } if (inheritInfo != null) { @@ -686,28 +690,24 @@ public class BeanDescriptor implements BeanType { if (isEmbedded()) { // initialise all the properties for (BeanProperty prop : propertiesAll()) { - prop.initialise(); + prop.initialise(initContext); } } else { // initialise just the Id properties if (idProperty != null) { - idProperty.initialise(); + idProperty.initialise(initContext); } } } /** * Initialise the exported and imported parts for associated properties. - * - * @param asOfTableMap the map of base tables to associated 'with history' tables - * @param asOfViewSuffix the suffix added to the table name to derive the 'with history' view name - * @param draftTableMap the map of base tables to associated 'draft' tables. */ - public void initialiseOther(Map asOfTableMap, String asOfViewSuffix, Map draftTableMap) { + public void initialiseOther(BeanDescriptorInitContext initContext) { - for (BeanPropertyAssocMany aPropertiesManyToMany1 : propertiesManyToMany) { + for (BeanPropertyAssocMany many : propertiesManyToMany) { // register associated draft table for M2M intersection - aPropertiesManyToMany1.registerDraftIntersectionTable(draftTableMap); + many.registerDraftIntersectionTable(initContext); } if (historySupport) { @@ -717,8 +717,7 @@ public class BeanDescriptor implements BeanType { // register associated history table for M2M intersection if (!aPropertiesManyToMany.isExcludedFromHistory()) { TableJoin intersectionTableJoin = aPropertiesManyToMany.getIntersectionTableJoin(); - String intersectionTableName = intersectionTableJoin.getTable(); - asOfTableMap.put(intersectionTableName, intersectionTableName + asOfViewSuffix); + initContext.addHistoryIntersection(intersectionTableJoin.getTable()); } } } @@ -727,14 +726,14 @@ public class BeanDescriptor implements BeanType { // initialise all the non-id properties for (BeanProperty prop : propertiesAll()) { if (!prop.isId()) { - prop.initialise(); + prop.initialise(initContext); } prop.registerColumn(this, null); } } if (unidirectional != null) { - unidirectional.initialise(); + unidirectional.initialise(initContext); } idBinder.initialise(); @@ -799,7 +798,7 @@ public class BeanDescriptor implements BeanType { if (propName == null) { return; } - props[i] = findBeanProperty(propName); + props[i] = findProperty(propName); } if (props.length == 1) { for (BeanProperty[] inserted : propertiesUnique) { @@ -1639,6 +1638,7 @@ public class BeanDescriptor implements BeanType { /** * Execute the postLoad if a BeanPostLoad exists for this bean. */ + @Override public void postLoad(Object bean) { if (beanPostLoad != null) { beanPostLoad.postLoad(bean); @@ -1787,6 +1787,11 @@ public class BeanDescriptor implements BeanType { return idBinder instanceof IdBinderSimple; } + @Override + public boolean hasId() { + return idProperty != null; + } + /** * Return true if this type has a simple Id and the platform supports mutli-value binding. */ @@ -1951,7 +1956,8 @@ public class BeanDescriptor implements BeanType { * Return the bean property traversing the object graph and taking into * account inheritance. */ - public BeanProperty getBeanPropertyFromPath(String path) { + @Override + public BeanProperty findPropertyFromPath(String path) { BeanDescriptor other = this; while (true) { @@ -1983,7 +1989,7 @@ public class BeanDescriptor implements BeanType { } String[] splitBegin = SplitName.splitBegin(path); - BeanProperty beanProperty = result.findBeanProperty(splitBegin[0]); + BeanProperty beanProperty = result.findProperty(splitBegin[0]); if (beanProperty instanceof BeanPropertyAssoc) { BeanPropertyAssoc assocProp = (BeanPropertyAssoc) beanProperty; path = splitBegin[1]; @@ -2224,7 +2230,7 @@ public class BeanDescriptor implements BeanType { @Override public Property getProperty(String propName) { - return findBeanProperty(propName); + return findProperty(propName); } /** @@ -2458,7 +2464,7 @@ public class BeanDescriptor implements BeanType { /** * Return a 'dynamic property' used to read a formula. */ - private SqlTreeProperty findSqlTreeFormula(String formulaExpression) { + private STreeProperty findSqlTreeFormula(String formulaExpression) { return dynamicProperty.computeIfAbsent(formulaExpression, (formula) -> new FormulaPropertyPath(this, formula).build()); } @@ -2468,7 +2474,8 @@ public class BeanDescriptor implements BeanType { * * The property can be a dynamic formula or a well known bean property. */ - public SqlTreeProperty findSqlTreeProperty(String propName) { + @Override + public STreeProperty findPropertyWithDynamic(String propName) { if (propName.indexOf('(') > -1) { return findSqlTreeFormula(propName); } @@ -2482,7 +2489,8 @@ public class BeanDescriptor implements BeanType { * inheritance tree (not up). *

*/ - public BeanProperty findBeanProperty(String propName) { + @Override + public BeanProperty findProperty(String propName) { int basePos = propName.indexOf('.'); if (basePos > -1) { // embedded property @@ -2540,6 +2548,10 @@ public class BeanDescriptor implements BeanType { return autoTunable; } + public boolean isElementType() { + return false; + } + /** * Returns the Inheritance mapping information. This will be null if this type * of bean is not involved in any ORM inheritance mapping. @@ -2809,6 +2821,41 @@ public class BeanDescriptor implements BeanType { return draftableElement; } + @Override + public boolean isEmbeddedPath(String propertyPath) { + ElPropertyDeploy elProp = getElPropertyDeploy(propertyPath); + if (elProp == null) { + throw new PersistenceException("Invalid path " + propertyPath + " from " + getFullName()); + } + return elProp.getBeanProperty().isEmbedded(); + } + + @Override + public ExtraJoin extraJoin(String propertyPath) { + + ElPropertyValue elGetValue = getElGetValue(propertyPath); + if (elGetValue != null) { + BeanProperty beanProperty = elGetValue.getBeanProperty(); + if (beanProperty instanceof BeanPropertyAssoc) { + BeanPropertyAssoc assocProp = (BeanPropertyAssoc) beanProperty; + if (!assocProp.isEmbedded()) { + return new ExtraJoin(assocProp, elGetValue.containsMany()); + } + } + } + return null; + } + + @Override + public void inheritanceLoad(SqlBeanLoad sqlBeanLoad, STreeProperty property, DbReadContext ctx) { + BeanProperty p = getBeanProperty(property.getName()); + if (p != null) { + p.load(sqlBeanLoad); + } else { + property.loadIgnore(ctx); + } + } + public void setUnmappedJson(EntityBean bean, Map unmappedProperties) { if (unmappedJson != null) { unmappedJson.setValueIntercept(bean, unmappedProperties); @@ -2933,16 +2980,6 @@ public class BeanDescriptor implements BeanType { return selectLastInsertedId; } - /** - * Return the TableJoins. - *

- * For properties mapped to secondary tables rather than the base table. - *

- */ - public TableJoin[] tableJoins() { - return derivedTableJoins; - } - @Override public Collection allProperties() { return propertiesAll(); @@ -3131,6 +3168,26 @@ public class BeanDescriptor implements BeanType { } } + @Override + public STreeProperty[] propsBaseScalar() { + return propertiesBaseScalar; + } + + @Override + public STreePropertyAssoc[] propsEmbedded() { + return propertiesEmbedded; + } + + @Override + public STreePropertyAssocOne[] propsOne() { + return propertiesOne; + } + + @Override + public STreePropertyAssocMany[] propsMany() { + return propertiesMany; + } + /** * All the BeanPropertyAssocOne that are not embedded. These are effectively * joined beans. For ManyToOne and OneToOne associations. diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorElement.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorElement.java new file mode 100644 index 000000000..dd793505c --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorElement.java @@ -0,0 +1,24 @@ +package io.ebeaninternal.server.deploy; + +import io.ebean.bean.EntityBean; +import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; + +/** + * Bean descriptor used with ElementCollection (where we don't have a mapped type/class). + */ +public class BeanDescriptorElement extends BeanDescriptor { + + public BeanDescriptorElement(BeanDescriptorMap owner, DeployBeanDescriptor deploy) { + super(owner, deploy); + } + + public boolean isElementType() { + return true; + } + + @Override + protected EntityBean createPrototypeEntityBean(Class beanType) { + return new ElementEntityBean(properties); + } + +} diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorInitContext.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorInitContext.java new file mode 100644 index 000000000..ef367199d --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorInitContext.java @@ -0,0 +1,32 @@ +package io.ebeaninternal.server.deploy; + +import java.util.Map; + +class BeanDescriptorInitContext { + + private final Map withHistoryTables; + private final Map draftTables; + private final String asOfViewSuffix; + + BeanDescriptorInitContext(Map withHistoryTables, Map draftTables, String asOfViewSuffix) { + this.withHistoryTables = withHistoryTables; + this.draftTables = draftTables; + this.asOfViewSuffix = asOfViewSuffix; + } + + void addDraft(String baseTable, String draftTable) { + draftTables.put(baseTable, draftTable); + } + + void addHistory(String baseTable, String baseTableAsOf) { + withHistoryTables.put(baseTable, baseTableAsOf); + } + + void addHistoryIntersection(String intersectionTableName) { + withHistoryTables.put(intersectionTableName, intersectionTableName + asOfViewSuffix); + } + + void addDraftIntersection(String intersectionPublishTable, String intersectionDraftTable) { + draftTables.put(intersectionPublishTable, intersectionDraftTable); + } +} diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java index 6bd12fda2..5824612bd 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java @@ -184,8 +184,6 @@ public class BeanDescriptorManager implements BeanDescriptorMap { private final BeanLifecycleAdapterFactory beanLifecycleAdapterFactory; - private final boolean eagerFetchLobs; - private final String asOfViewSuffix; /** @@ -215,12 +213,11 @@ public class BeanDescriptorManager implements BeanDescriptorMap { this.databasePlatform = serverConfig.getDatabasePlatform(); this.multiValueBind = config.getMultiValueBind(); this.idBinderFactory = new IdBinderFactory(databasePlatform.isIdInExpandedForm(), multiValueBind); - this.eagerFetchLobs = serverConfig.isEagerFetchLobs(); this.queryPlanTTLSeconds = serverConfig.getQueryPlanTTLSeconds(); this.asOfViewSuffix = getAsOfViewSuffix(databasePlatform, serverConfig); String versionsBetweenSuffix = getVersionsBetweenSuffix(databasePlatform, serverConfig); - this.readAnnotations = new ReadAnnotations(config.getGeneratedPropertyFactory(), asOfViewSuffix, versionsBetweenSuffix, serverConfig.isDisableL2Cache()); + this.readAnnotations = new ReadAnnotations(config.getGeneratedPropertyFactory(), asOfViewSuffix, versionsBetweenSuffix, serverConfig); this.bootupClasses = config.getBootupClasses(); this.createProperties = config.getDeployCreateProperties(); this.namingConvention = serverConfig.getNamingConvention(); @@ -575,12 +572,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap { // we can initialise them which sorts out circular // dependencies for OneToMany and ManyToOne etc + BeanDescriptorInitContext initContext = new BeanDescriptorInitContext(asOfTableMap, draftTableMap, asOfViewSuffix); + // PASS 1: // initialise the ID properties of all the beans // first (as they are needed to initialise the // associated properties in the second pass). for (BeanDescriptor d : descMap.values()) { - d.initialiseId(asOfTableMap, draftTableMap); + d.initialiseId(initContext); } // PASS 2: @@ -595,7 +594,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap { // also look for intersection tables with // associated history support and register them // into the asOfTableMap - d.initialiseOther(asOfTableMap, asOfViewSuffix, draftTableMap); + d.initialiseOther(initContext); } // PASS 4: @@ -656,6 +655,13 @@ public class BeanDescriptorManager implements BeanDescriptorMap { return beanTableMap.get(type); } + /** + * Return a BeanTable for an ElementCollection. + */ + public BeanTable getCollectionBeanTable(String fullTableName, Class targetType) { + return new BeanTable(this, fullTableName, targetType); + } + @SuppressWarnings("unchecked") public BeanManager getBeanManager(Class entityType) { @@ -1025,7 +1031,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap { *

*/ @SuppressWarnings({"unchecked", "rawtypes"}) - private void makeUnidirectional(DeployBeanInfo info, DeployBeanPropertyAssocMany oneToMany) { + private void makeUnidirectional(DeployBeanPropertyAssocMany oneToMany) { DeployBeanDescriptor targetDesc = getTargetDescriptor(oneToMany); @@ -1045,34 +1051,36 @@ public class BeanDescriptorManager implements BeanDescriptorMap { // mark this property as unidirectional oneToMany.setUnidirectional(); - // create the 'shadow' unidirectional property - // which is put on the target descriptor - DeployBeanPropertyAssocOne unidirectional = new DeployBeanPropertyAssocOne(targetDesc, owningType); - unidirectional.setUndirectionalShadow(); - unidirectional.setNullable(false); - unidirectional.setDbRead(true); - unidirectional.setDbInsertable(true); - unidirectional.setDbUpdateable(false); - - targetDesc.setUnidirectional(unidirectional); - // specify table and table alias... BeanTable beanTable = getBeanTable(owningType); - unidirectional.setBeanTable(beanTable); - unidirectional.setName(beanTable.getBaseTable()); - - info.setBeanJoinType(unidirectional, true); // define the TableJoin DeployTableJoin oneToManyJoin = oneToMany.getTableJoin(); if (!oneToManyJoin.hasJoinColumns()) { throw new RuntimeException("No join columns"); } + createUnidirectional(targetDesc, owningType, beanTable, oneToManyJoin); + } - // inverse of the oneToManyJoin - DeployTableJoin unidirectionalJoin = unidirectional.getTableJoin(); - unidirectionalJoin.setColumns(oneToManyJoin.columns(), true); + /** + * Create and add a Unidirectional property (for ElementCollection) which maps to the foreign key. + */ + public void createUnidirectional(DeployBeanDescriptor targetDesc, Class targetType, BeanTable beanTable, DeployTableJoin oneToManyJoin) { + // create the 'shadow' unidirectional property + // which is put on the target descriptor + DeployBeanPropertyAssocOne unidirectional = new DeployBeanPropertyAssocOne<>(targetDesc, targetType); + unidirectional.setUndirectionalShadow(); + unidirectional.setNullable(false); + unidirectional.setDbRead(true); + unidirectional.setDbInsertable(true); + unidirectional.setDbUpdateable(false); + unidirectional.setBeanTable(beanTable); + unidirectional.setName(beanTable.getBaseTable()); + unidirectional.setJoinType(true); + unidirectional.setJoinColumns(oneToManyJoin.columns(), true); + + targetDesc.setUnidirectional(unidirectional); } private void checkMappedByOneToOne(DeployBeanPropertyAssocOne prop) { @@ -1137,6 +1145,10 @@ public class BeanDescriptorManager implements BeanDescriptorMap { */ private void checkMappedByOneToMany(DeployBeanInfo info, DeployBeanPropertyAssocMany prop) { + if (prop.isElementCollection()) { + // skip mapping check + return; + } DeployBeanDescriptor targetDesc = getTargetDescriptor(prop); if (targetDesc.isDraftableElement()) { @@ -1159,7 +1171,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap { if (!findMappedBy(prop)) { if (!prop.isO2mJoinTable()) { - makeUnidirectional(info, prop); + makeUnidirectional(prop); } return; } @@ -1311,7 +1323,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap { DeployBeanInfo info = new DeployBeanInfo<>(deployUtil, desc); - readAnnotations.readInitial(info, eagerFetchLobs); + readAnnotations.readInitial(info); return info; } @@ -1636,6 +1648,20 @@ public class BeanDescriptorManager implements BeanDescriptorMap { target.setPrimaryKeyJoin(inverseJoin); } + /** + * Create a DeployBeanDescriptor for an ElementCollection target. + */ + public DeployBeanDescriptor createDeployDescriptor(Class targetType) { + return new DeployBeanDescriptor<>(this, targetType, serverConfig); + } + + /** + * Create a BeanDescriptor for an ElementCollection target. + */ + public BeanDescriptor createElementDescriptor(DeployBeanDescriptor elementDescriptor) { + return new BeanDescriptorElement<>(this, elementDescriptor); + } + public void visitMetrics(MetricVisitor visitor) { for (BeanDescriptor desc : immutableDescriptorList) { desc.visitMetrics(visitor); diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanListHelp.java b/src/main/java/io/ebeaninternal/server/deploy/BeanListHelp.java index df5429233..338bfb5a9 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanListHelp.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanListHelp.java @@ -5,7 +5,6 @@ import io.ebean.Query; import io.ebean.Transaction; import io.ebean.bean.BeanCollection; import io.ebean.bean.BeanCollectionAdd; -import io.ebean.bean.BeanCollectionLoader; import io.ebean.bean.EntityBean; import io.ebean.common.BeanList; import io.ebeaninternal.server.text.json.SpiJsonWriter; @@ -17,48 +16,20 @@ import java.util.List; /** * Helper object for dealing with Lists. */ -public final class BeanListHelp extends BaseCollectionHelp { +public class BeanListHelp extends BaseCollectionHelp { - private final BeanPropertyAssocMany many; - private final BeanDescriptor targetDescriptor; - private final String propertyName; - - private BeanCollectionLoader loader; - - public BeanListHelp(BeanPropertyAssocMany many) { - this.many = many; - this.targetDescriptor = many.getTargetDescriptor(); - this.propertyName = many.getName(); + BeanListHelp(BeanPropertyAssocMany many) { + super(many); } - public BeanListHelp() { - this.many = null; - this.targetDescriptor = null; - this.propertyName = null; - } - - @Override - public void setLoader(BeanCollectionLoader loader) { - this.loader = loader; - } - - /** - * Internal add bypassing any modify listening. - */ - @Override - public void add(BeanCollection collection, EntityBean bean, boolean withCheck) { - if (withCheck) { - collection.internalAddWithCheck(bean); - } else { - collection.internalAdd(bean); - } + BeanListHelp() { + super(); } @Override public BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) { if (bc instanceof BeanList) { - BeanList bl = (BeanList) bc; if (bl.getActualList() == null) { bl.setActualList(new ArrayList<>()); @@ -144,13 +115,7 @@ public final class BeanListHelp extends BaseCollectionHelp { list = (List) collection; } - if (!list.isEmpty() || ctx.isIncludeEmpty()) { - ctx.beginAssocMany(name); - for (Object aList : list) { - targetDescriptor.jsonWrite(ctx, (EntityBean) aList); - } - ctx.endAssocMany(); - } + jsonWriteCollection(ctx, name, list); } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanListHelpElement.java b/src/main/java/io/ebeaninternal/server/deploy/BeanListHelpElement.java new file mode 100644 index 000000000..2a0b4de96 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanListHelpElement.java @@ -0,0 +1,37 @@ +package io.ebeaninternal.server.deploy; + +import io.ebean.bean.BeanCollection; +import io.ebean.bean.BeanCollectionAdd; +import io.ebean.bean.EntityBean; +import io.ebeaninternal.server.text.json.SpiJsonWriter; + +/** + * Helper for element collection List. + */ +public class BeanListHelpElement extends BeanListHelp { + + BeanListHelpElement(BeanPropertyAssocMany many) { + super(many); + } + + @Override + public void add(BeanCollection collection, EntityBean bean, boolean withCheck) { + Object elementValue = bean._ebean_getField(0); + if (withCheck) { + collection.internalAddWithCheck(elementValue); + } else { + collection.internalAdd(elementValue); + } + } + + @Override + public BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) { + return super.getBeanCollectionAdd(bc, mapKey); + } + + @Override + void jsonWriteElement(SpiJsonWriter ctx, Object element) { + many.jsonWriteValue(ctx, element); + //targetDescriptor.jsonWrite(ctx, (EntityBean) element); + } +} diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanMapHelp.java b/src/main/java/io/ebeaninternal/server/deploy/BeanMapHelp.java index e3b42bea7..4c4e76190 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanMapHelp.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanMapHelp.java @@ -5,7 +5,6 @@ import io.ebean.Query; import io.ebean.Transaction; import io.ebean.bean.BeanCollection; import io.ebean.bean.BeanCollectionAdd; -import io.ebean.bean.BeanCollectionLoader; import io.ebean.bean.EntityBean; import io.ebean.common.BeanMap; import io.ebeaninternal.server.text.json.SpiJsonWriter; @@ -24,12 +23,11 @@ public final class BeanMapHelp extends BaseCollectionHelp { private final BeanDescriptor targetDescriptor; private final String propertyName; private final BeanProperty beanProperty; - private BeanCollectionLoader loader; /** * When created for a given query that will return a map. */ - public BeanMapHelp(BeanDescriptor targetDescriptor, String mapKey) { + BeanMapHelp(BeanDescriptor targetDescriptor, String mapKey) { this.targetDescriptor = targetDescriptor; this.beanProperty = targetDescriptor.getBeanProperty(mapKey); this.many = null; @@ -39,18 +37,13 @@ public final class BeanMapHelp extends BaseCollectionHelp { /** * When help is attached to a specific many property. */ - public BeanMapHelp(BeanPropertyAssocMany many) { + BeanMapHelp(BeanPropertyAssocMany many) { this.many = many; this.targetDescriptor = many.getTargetDescriptor(); this.propertyName = many.getName(); this.beanProperty = targetDescriptor.getBeanProperty(many.getMapKey()); } - @Override - public void setLoader(BeanCollectionLoader loader) { - this.loader = loader; - } - @Override @SuppressWarnings("unchecked") public BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) { diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java b/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java index a7260f269..5ea987763 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java @@ -22,9 +22,9 @@ import io.ebeaninternal.server.el.ElPropertyChainBuilder; import io.ebeaninternal.server.el.ElPropertyValue; import io.ebeaninternal.server.properties.BeanPropertyGetter; import io.ebeaninternal.server.properties.BeanPropertySetter; +import io.ebeaninternal.server.query.STreeProperty; import io.ebeaninternal.server.query.SqlBeanLoad; import io.ebeaninternal.server.query.SqlJoinType; -import io.ebeaninternal.server.query.SqlTreeProperty; import io.ebeaninternal.server.text.json.ReadJson; import io.ebeaninternal.server.text.json.SpiJsonWriter; import io.ebeaninternal.server.type.DataBind; @@ -56,7 +56,7 @@ import java.util.Set; * Description of a property of a bean. Includes its deployment information such * as database column mapping information. */ -public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty { +public class BeanProperty implements ElPropertyValue, Property, STreeProperty { private static final Logger logger = LoggerFactory.getLogger(BeanProperty.class); @@ -455,11 +455,10 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty * initialise variables that can't be done in construction due to recursive * issues. */ - public void initialise() { + public void initialise(BeanDescriptorInitContext initContext) { // do nothing for normal BeanProperty if (!isTransient && scalarType == null) { - String msg = "No ScalarType assigned to " + descriptor.getFullName() + "." + getName(); - throw new RuntimeException(msg); + throw new RuntimeException("No ScalarType assigned to " + descriptor.getFullName() + "." + getName()); } } @@ -505,6 +504,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty /** * Return true if this property is based on a formula. */ + @Override public boolean isFormula() { return formula; } @@ -530,6 +530,11 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty return descriptor.getEncryptKey(this); } + @Override + public String getEncryptKeyAsString() { + return getEncryptKey().getStringValue(); + } + public String getDecryptProperty(String propertyName) { return dbEncryptFunction.getDecryptSql(propertyName); } @@ -552,6 +557,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty * Add any extra joins required to support this property. Generally a no * operation except for a OneToOne exported. */ + @Override public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) { if (formula && sqlFormulaJoin != null) { ctx.appendFormulaJoin(sqlFormulaJoin, joinType); @@ -576,6 +582,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty return aggregation != null; } + @Override public void appendSelect(DbSqlContext ctx, boolean subQuery) { if (aggregation != null) { @@ -586,8 +593,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty } else if (!isTransient && !ignoreDraftOnlyProperty(ctx.isDraftQuery())) { if (secondaryTableJoin != null) { - String relativePrefix = ctx.getRelativePrefix(secondaryTableJoinPrefix); - ctx.pushTableAlias(relativePrefix); + ctx.pushTableAlias(ctx.getRelativePrefix(secondaryTableJoinPrefix)); } if (dbEncrypted) { @@ -614,14 +620,17 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty return owningType.isAssignableFrom(type); } + @Override public void loadIgnore(DbReadContext ctx) { scalarType.loadIgnore(ctx.getDataReader()); } + @Override public void load(SqlBeanLoad sqlBeanLoad) { sqlBeanLoad.load(this); } + @Override public void buildRawSqlSelectChain(String prefix, List selectChain) { if (prefix == null) { selectChain.add(name); @@ -808,13 +817,6 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty setValue(bean, cacheData); } - /** - * Get the property value from a compound value type. - */ - public Object getValueObject(Object bean) { - throw new RuntimeException("Expected to be called only on BeanPropertyCompoundScalar"); - } - @Override public Object getVal(Object bean) { return getValue((EntityBean) bean); @@ -1368,6 +1370,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty /** * Return true if this is included in the unique id. */ + @Override public boolean isId() { return id; } @@ -1376,6 +1379,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty * Return true if this is an Embedded property. In this case it shares the * table and primary key of its owner object. */ + @Override public boolean isEmbedded() { return embedded; } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java index 656f5d0bc..cbe81bcbb 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java @@ -1,10 +1,11 @@ package io.ebeaninternal.server.deploy; -import io.ebean.EbeanServer; import io.ebean.Query; import io.ebean.bean.EntityBean; import io.ebean.text.PathProperties; import io.ebean.util.SplitName; +import io.ebeaninternal.api.SpiEbeanServer; +import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.server.core.DefaultSqlUpdate; import io.ebeaninternal.server.core.InternString; import io.ebeaninternal.server.deploy.id.IdBinder; @@ -15,7 +16,10 @@ import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc; import io.ebeaninternal.server.el.ElPropertyChainBuilder; import io.ebeaninternal.server.el.ElPropertyValue; import io.ebeaninternal.server.persist.MultiValueWrapper; +import io.ebeaninternal.server.query.STreePropertyAssoc; +import io.ebeaninternal.server.query.STreeType; import io.ebeaninternal.server.query.SqlJoinType; +import io.ebeaninternal.server.querydefn.DefaultOrmQuery; import io.ebeanservice.docstore.api.mapping.DocMappingBuilder; import io.ebeanservice.docstore.api.mapping.DocPropertyMapping; import io.ebeanservice.docstore.api.mapping.DocPropertyType; @@ -30,7 +34,7 @@ import java.util.List; /** * Abstract base for properties mapped to an associated bean, list, set or map. */ -public abstract class BeanPropertyAssoc extends BeanProperty { +public abstract class BeanPropertyAssoc extends BeanProperty implements STreePropertyAssoc { private static final Logger logger = LoggerFactory.getLogger(BeanPropertyAssoc.class); @@ -101,15 +105,18 @@ public abstract class BeanPropertyAssoc extends BeanProperty { * Initialise post construction. */ @Override - public void initialise() { + public void initialise(BeanDescriptorInitContext initContext) { // this *MUST* execute after the BeanDescriptor is // put into the map to stop infinite recursion + initialiseTargetDescriptor(initContext); + } + + void initialiseTargetDescriptor(BeanDescriptorInitContext initContext) { targetDescriptor = descriptor.getBeanDescriptor(targetType); if (!isTransient) { targetIdBinder = targetDescriptor.getIdBinder(); targetInheritInfo = targetDescriptor.getInheritInfo(); saveRecurseSkippable = targetDescriptor.isSaveRecurseSkippable(); - if (!targetIdBinder.isComplexId()) { targetIdProperty = targetIdBinder.getIdProperty(); } @@ -142,6 +149,7 @@ public abstract class BeanPropertyAssoc extends BeanProperty { /** * Add table join with table alias based on prefix. */ + @Override public SqlJoinType addJoin(SqlJoinType joinType, String prefix, DbSqlContext ctx) { return tableJoin.addJoin(joinType, prefix, ctx); } @@ -149,6 +157,7 @@ public abstract class BeanPropertyAssoc extends BeanProperty { /** * Add table join with explicit table alias. */ + @Override public SqlJoinType addJoin(SqlJoinType joinType, String a1, String a2, DbSqlContext ctx) { return tableJoin.addJoin(joinType, a1, a2, ctx); } @@ -186,6 +195,29 @@ public abstract class BeanPropertyAssoc extends BeanProperty { return targetDescriptor; } + SpiEbeanServer server() { + return descriptor.getEbeanServer(); + } + + /** + * Create a new query for the target type. + * + * We use target descriptor rather than target property type to support ElementCollection. + */ + public SpiQuery newQuery(SpiEbeanServer server) { + return new DefaultOrmQuery<>(targetDescriptor, server, server.getExpressionFactory()); + } + + @Override + public IdBinder getIdBinder() { + return descriptor.getIdBinder(); + } + + @Override + public STreeType target() { + return targetDescriptor; + } + /** * Return true if the target side has soft delete. */ @@ -325,6 +357,13 @@ public abstract class BeanPropertyAssoc extends BeanProperty { return tableJoin.columns().length <= 0 || tableJoin.columns()[0].isInsertable(); } + /** + * Return the underlying BeanTable for this property. + */ + public BeanTable getBeanTable() { + return beanTable; + } + /** * return the join to use for the bean. */ @@ -440,10 +479,6 @@ public abstract class BeanPropertyAssoc extends BeanProperty { } } - EbeanServer server() { - return getBeanDescriptor().getEbeanServer(); - } - void bindParentIds(DefaultSqlUpdate delete, List parentIds) { if (isExportedSimple()) { @@ -492,4 +527,26 @@ public abstract class BeanPropertyAssoc extends BeanProperty { private boolean isExportedSimple() { return exportedProperties.length == 1; } + + /** + * Find and return the exported property matching to this property. + */ + ExportedProperty findMatch(boolean embedded, BeanProperty prop, String matchColumn, TableJoin tableJoin) { + + String searchTable = tableJoin.getTable(); + + for (TableJoinColumn column : tableJoin.columns()) { + String matchTo = column.getLocalDbColumn(); + + if (matchColumn.equalsIgnoreCase(matchTo)) { + String foreignCol = column.getForeignDbColumn(); + return new ExportedProperty(embedded, foreignCol, prop); + } + } + + String msg = "Error with the Join on [" + getFullBeanName() + + "]. Could not find the matching foreign key for [" + matchColumn + "] in table[" + searchTable + "]?" + + " Perhaps using a @JoinColumn with the name/referencedColumnName attributes swapped?"; + throw new PersistenceException(msg); + } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java index f193624b0..42d3e0667 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java @@ -1,7 +1,5 @@ package io.ebeaninternal.server.deploy; -import io.ebean.EbeanServer; -import io.ebean.Query; import io.ebean.SqlUpdate; import io.ebean.Transaction; import io.ebean.bean.BeanCollection; @@ -12,11 +10,11 @@ import io.ebean.bean.EntityBean; import io.ebean.text.PathProperties; import io.ebeaninternal.api.SpiExpressionRequest; import io.ebeaninternal.api.SpiQuery; -import io.ebeaninternal.server.core.DefaultSqlUpdate; import io.ebeaninternal.server.deploy.id.ImportedId; import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany; import io.ebeaninternal.server.el.ElPropertyChainBuilder; import io.ebeaninternal.server.el.ElPropertyValue; +import io.ebeaninternal.server.query.STreePropertyAssocMany; import io.ebeaninternal.server.query.SqlBeanLoad; import io.ebeaninternal.server.text.json.ReadJson; import io.ebeaninternal.server.text.json.SpiJsonWriter; @@ -35,7 +33,7 @@ import java.util.Map; /** * Property mapped to a List Set or Map. */ -public class BeanPropertyAssocMany extends BeanPropertyAssoc { +public class BeanPropertyAssocMany extends BeanPropertyAssoc implements STreePropertyAssocMany { private static final Logger logger = LoggerFactory.getLogger(BeanPropertyAssocMany.class); @@ -51,7 +49,7 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { /** * For ManyToMany this is the Inverse join used to build reference queries. */ - private final TableJoin inverseJoin; + final TableJoin inverseJoin; /** * Flag to indicate that this is a unidirectional relationship. @@ -70,6 +68,8 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { */ private final boolean manyToMany; + private final boolean elementCollection; + /** * Order by used when fetch joining the associated many. */ @@ -91,8 +91,6 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { private BeanProperty mapKeyProperty; - private String exportedPropertyBindProto = "?"; - /** * Property on the 'child' bean that links back to the 'master'. */ @@ -106,9 +104,7 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { private ImportedId importedId; - private String deleteByParentIdSql; - - private String deleteByParentIdInSql; + private BeanPropertyAssocManySqlHelp sqlHelp; /** * Create this property. @@ -119,6 +115,7 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { this.o2mJoinTable = deploy.isO2mJoinTable(); this.hasOrderColumn = deploy.hasOrderColumn(); this.manyToMany = deploy.isManyToMany(); + this.elementCollection = deploy.isElementCollection(); this.manyType = deploy.getManyType(); this.mapKey = deploy.getMapKey(); this.fetchOrderBy = deploy.getFetchOrderBy(); @@ -136,13 +133,15 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { } @Override - public void initialise() { - super.initialise(); + public void initialise(BeanDescriptorInitContext initContext) { + super.initialise(initContext); + initialiseAssocMany(); + } + private void initialiseAssocMany() { if (!isTransient) { this.help = BeanCollectionHelpFactory.create(this); - - if (hasJoinTable()) { + if (hasJoinTable() || elementCollection) { importedId = createImportedId(this, targetDescriptor, tableJoin); } else { @@ -159,37 +158,21 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { } exportedProperties = createExported(); + this.sqlHelp = new BeanPropertyAssocManySqlHelp<>(this, exportedProperties); + if (exportedProperties.length > 0) { embeddedExportedProperties = exportedProperties[0].isEmbedded(); - exportedPropertyBindProto = deriveExportedPropertyBindProto(); - if (fetchOrderBy != null) { - // derive lazyFetchOrderBy - StringBuilder sb = new StringBuilder(50); - for (int i = 0; i < exportedProperties.length; i++) { - if (i > 0) { - sb.append(", "); - } - // these fk columns are either on the intersection (int_) or base table (t0) - String fkTableAlias = hasJoinTable() ? "int_" : "t0"; - sb.append(fkTableAlias).append(".").append(exportedProperties[i].getForeignDbColumn()); - } - sb.append(", ").append(fetchOrderBy); - lazyFetchOrderBy = sb.toString().trim(); + lazyFetchOrderBy = sqlHelp.lazyFetchOrderBy(fetchOrderBy); } } - - String delStmt; - if (hasJoinTable()) { - delStmt = "delete from " + inverseJoin.getTable() + " where "; - } else { - delStmt = "delete from " + targetDescriptor.getBaseTable() + " where "; - } - deleteByParentIdSql = delStmt + deriveWhereParentIdSql(false, ""); - deleteByParentIdInSql = delStmt + deriveWhereParentIdSql(true, ""); } } + String targetTable() { + return targetDescriptor.getBaseTable(); + } + /** * Initialise after the target bean descriptors have been all set. */ @@ -295,44 +278,23 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { public SqlUpdate deleteByParentId(Object parentId, List parentIdist) { if (parentId != null) { - return deleteByParentId(parentId); + return sqlHelp.deleteByParentId(parentId); } else { - return deleteByParentIdList(parentIdist); + return sqlHelp.deleteByParentIdList(parentIdist); } } - private SqlUpdate deleteByParentId(Object parentId) { - DefaultSqlUpdate sqlDelete = new DefaultSqlUpdate(deleteByParentIdSql); - bindParentId(sqlDelete, parentId); - return sqlDelete; - } - /** * Find the Id's of detail beans given a parent Id or list of parent Id's. */ public List findIdsByParentId(Object parentId, List parentIdList, Transaction t, List excludeDetailIds) { if (parentId != null) { - return findIdsByParentId(parentId, t, excludeDetailIds); + return sqlHelp.findIdsByParentId(parentId, t, excludeDetailIds); } else { - return findIdsByParentIdList(parentIdList, t, excludeDetailIds); + return sqlHelp.findIdsByParentIdList(parentIdList, t, excludeDetailIds); } } - private List findIdsByParentId(Object parentId, Transaction t, List excludeDetailIds) { - - String rawWhere = deriveWhereParentIdSql(false, ""); - - EbeanServer server = server(); - Query q = server.find(getPropertyType()); - bindParentIdEq(rawWhere, parentId, q); - - if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) { - q.where().not(q.getExpressionFactory().idIn(excludeDetailIds)); - } - - return server.findIds(q, t); - } - /** * Exclude many properties from bean cache data. */ @@ -359,88 +321,10 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { // assumes the ManyToOne property is included query.where().in(childMasterIdProperty, parentIds); } else { - addWhereParentIdIn(query, parentIds); + sqlHelp.addWhereParentIdIn(query, parentIds); } } - /** - * Add a where clause to the query for a given list of parent Id's. - */ - private void addWhereParentIdIn(SpiQuery query, List parentIds) { - - String tableAlias = hasJoinTable() ? "int_." : "t0."; - if (hasJoinTable()) { - query.setM2MIncludeJoin(inverseJoin); - } - String rawWhere = deriveWhereParentIdSql(true, tableAlias); - String expr = descriptor.getParentIdInExpr(parentIds.size(), rawWhere); - - bindParentIdsIn(expr, parentIds, query); - } - - private List findIdsByParentIdList(List parentIds, Transaction t, List excludeDetailIds) { - - String rawWhere = deriveWhereParentIdSql(true, ""); - String inClause = buildInClauseBinding(parentIds.size(), exportedPropertyBindProto); - - String expr = rawWhere + inClause; - - EbeanServer server = descriptor.getEbeanServer(); - Query q = server.find(propertyType); - bindParentIdsIn(expr, parentIds, q); - - if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) { - q.where().not(q.getExpressionFactory().idIn(excludeDetailIds)); - } - - return server.findIds(q, t); - } - - private SqlUpdate deleteByParentIdList(List parentIds) { - - StringBuilder sb = new StringBuilder(100); - sb.append(deleteByParentIdInSql); - sb.append(buildInClauseBinding(parentIds.size(), exportedPropertyBindProto)); - - DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString()); - bindParentIds(delete, parentIds); - return delete; - } - - private String deriveExportedPropertyBindProto() { - if (exportedProperties.length == 1) { - return "?"; - } - StringBuilder sb = new StringBuilder(); - sb.append("("); - for (int i = 0; i < exportedProperties.length; i++) { - if (i > 0) { - sb.append(","); - } - sb.append("?"); - } - sb.append(")"); - return sb.toString(); - } - - private String buildInClauseBinding(int size, String bindProto) { - - if (descriptor.isSimpleId()) { - return descriptor.getIdBinder().getIdInValueExpr(false, size); - } - StringBuilder sb = new StringBuilder(10 + (size * (bindProto.length() + 1))); - sb.append(" in"); - sb.append(" ("); - for (int i = 0; i < size; i++) { - if (i > 0) { - sb.append(","); - } - sb.append(bindProto); - } - sb.append(") "); - return sb.toString(); - } - /** * Set the lazy load server to help create reference collections (that lazy * load on demand). @@ -476,7 +360,7 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { } @Override - public Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException { + public Object readSet(DbReadContext ctx, EntityBean bean) { return null; } @@ -616,6 +500,10 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { return manyToMany; } + public boolean isElementCollection() { + return elementCollection; + } + /** * ManyToMany only, join from local table to intersection table. */ @@ -705,30 +593,6 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { } } - private String deriveWhereParentIdSql(boolean inClause, String tableAlias) { - - StringBuilder sb = new StringBuilder(); - - if (inClause) { - sb.append("("); - } - for (int i = 0; i < exportedProperties.length; i++) { - String fkColumn = exportedProperties[i].getForeignDbColumn(); - if (i > 0) { - String s = inClause ? "," : " and "; - sb.append(s); - } - sb.append(tableAlias).append(fkColumn); - if (!inClause) { - sb.append("=? "); - } - } - if (inClause) { - sb.append(")"); - } - return sb.toString(); - } - /** * Create the array of ExportedProperty used to build reference objects. */ @@ -741,12 +605,9 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { if (idProp != null && idProp.isEmbedded()) { BeanPropertyAssocOne one = (BeanPropertyAssocOne) idProp; - BeanDescriptor targetDesc = one.getTargetDescriptor(); - BeanProperty[] emIds = targetDesc.propertiesBaseScalar(); try { - for (BeanProperty emId : emIds) { - ExportedProperty expProp = findMatch(true, emId); - list.add(expProp); + for (BeanProperty emId : one.getTargetDescriptor().propertiesBaseScalar()) { + list.add(findMatch(true, emId)); } } catch (PersistenceException e) { // not found as individual scalar properties @@ -755,8 +616,7 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { } else { if (idProp != null) { - ExportedProperty expProp = findMatch(false, idProp); - list.add(expProp); + list.add(findMatch(false, idProp)); } } @@ -768,32 +628,12 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { */ private ExportedProperty findMatch(boolean embedded, BeanProperty prop) { - String matchColumn = prop.getDbColumn(); - - String searchTable; - TableJoinColumn[] columns; if (hasJoinTable()) { // look for column going to intersection - columns = intersectionJoin.columns(); - searchTable = intersectionJoin.getTable(); - + return findMatch(embedded, prop, prop.getDbColumn(), intersectionJoin); } else { - columns = tableJoin.columns(); - searchTable = tableJoin.getTable(); + return findMatch(embedded, prop, prop.getDbColumn(), tableJoin); } - for (TableJoinColumn column : columns) { - String matchTo = column.getLocalDbColumn(); - - if (matchColumn.equalsIgnoreCase(matchTo)) { - String foreignCol = column.getForeignDbColumn(); - return new ExportedProperty(embedded, foreignCol, prop); - } - } - - String msg = "Error with the Join on [" + getFullBeanName() - + "]. Could not find the matching foreign key for [" + matchColumn + "] in table[" + searchTable + "]?" - + " Perhaps using a @JoinColumn with the name/referencedColumnName attributes swapped?"; - throw new PersistenceException(msg); } /** @@ -813,8 +653,7 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { Class beanType = descriptor.getBeanType(); BeanDescriptor targetDesc = getTargetDescriptor(); - BeanPropertyAssocOne[] ones = targetDesc.propertiesOne(); - for (BeanPropertyAssocOne prop : ones) { + for (BeanPropertyAssocOne prop : targetDesc.propertiesOne()) { if (mappedBy != null) { // match using mappedBy as property name if (mappedBy.equalsIgnoreCase(prop.getName())) { @@ -880,9 +719,9 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { /** * Register the mapping of intersection table to associated draft table. */ - public void registerDraftIntersectionTable(Map draftTableMap) { + public void registerDraftIntersectionTable(BeanDescriptorInitContext initContext) { if (hasDraftIntersection()) { - draftTableMap.put(intersectionPublishTable, intersectionDraftTable); + initContext.addDraftIntersection(intersectionPublishTable, intersectionDraftTable); } } @@ -928,7 +767,7 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { * Skip JSON write value for ToMany property. */ @Override - public void jsonWriteValue(SpiJsonWriter writeJson, Object value) throws IOException { + public void jsonWriteValue(SpiJsonWriter writeJson, Object value) { // do nothing, exclude ToMany properties } @@ -1030,4 +869,16 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { public boolean isIncludeCascadeDelete() { return cascadeInfo.isDelete() || o2mJoinTable || ModifyListenMode.REMOVALS == modifyListenMode; } + + public String insertElementCollection() { + return sqlHelp.insertElementCollection(); + } + + public boolean isTargetDocStoreMapped() { + return targetDescriptor.isDocStoreMapped(); + } + + public BeanCollectionHelp getHelp() { + return help; + } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java index 258f74ae5..faa77fb49 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java @@ -13,7 +13,7 @@ import java.io.IOException; /** * Help BeanPropertyAssocMany with JSON processing. */ -public class BeanPropertyAssocManyJsonHelp { +class BeanPropertyAssocManyJsonHelp { /** * The associated many property. @@ -28,7 +28,7 @@ public class BeanPropertyAssocManyJsonHelp { /** * Construct for the owning many property. */ - public BeanPropertyAssocManyJsonHelp(BeanPropertyAssocMany many) { + BeanPropertyAssocManyJsonHelp(BeanPropertyAssocMany many) { this.many = many; boolean objectMapperPresent = many.getBeanDescriptor().getServerConfig().getClassLoadConfig().isJacksonObjectMapperPresent(); this.jsonTransient = !objectMapperPresent ? null : new BeanPropertyAssocManyJsonTransient(); diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonTransient.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonTransient.java index e00215e6c..fd1b9fe1d 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonTransient.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonTransient.java @@ -14,12 +14,12 @@ import java.util.LinkedHashMap; /** * Helper used to read transient many properties using Jackson ObjectMapper. */ -public class BeanPropertyAssocManyJsonTransient { +class BeanPropertyAssocManyJsonTransient { /** * Use Jackson ObjectMapper to read the transient 'many' property. */ - public void jsonReadUsingObjectMapper(BeanPropertyAssocMany many, ReadJson readJson, EntityBean parentBean) throws IOException { + void jsonReadUsingObjectMapper(BeanPropertyAssocMany many, ReadJson readJson, EntityBean parentBean) throws IOException { ObjectMapper mapper = readJson.getObjectMapper(); diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManySqlHelp.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManySqlHelp.java new file mode 100644 index 000000000..3b3426aa3 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManySqlHelp.java @@ -0,0 +1,258 @@ +package io.ebeaninternal.server.deploy; + +import io.ebean.SqlUpdate; +import io.ebean.Transaction; +import io.ebeaninternal.api.SpiEbeanServer; +import io.ebeaninternal.api.SpiQuery; +import io.ebeaninternal.dbmigration.model.visitor.BaseTablePropertyVisitor; +import io.ebeaninternal.dbmigration.model.visitor.VisitAllUsing; +import io.ebeaninternal.server.core.DefaultSqlUpdate; + +import java.util.List; + +class BeanPropertyAssocManySqlHelp { + + private final BeanPropertyAssocMany many; + private final ExportedProperty[] exportedProperties; + private final boolean hasJoinTable; + private final BeanDescriptor descriptor; + private final String exportedPropertyBindProto; + private final String deleteByParentIdSql; + private final String deleteByParentIdInSql; + private final String elementCollectionInsertSql; + + BeanPropertyAssocManySqlHelp(BeanPropertyAssocMany many, ExportedProperty[] exportedProperties) { + this.many = many; + this.exportedProperties = exportedProperties; + this.hasJoinTable = many.hasJoinTable(); + this.descriptor = many.getBeanDescriptor(); + this.exportedPropertyBindProto = deriveExportedPropertyBindProto(); + + String delStmt; + if (hasJoinTable) { + delStmt = "delete from " + many.inverseJoin.getTable() + " where "; + } else { + delStmt = "delete from " + many.targetTable() + " where "; + } + deleteByParentIdSql = delStmt + deriveWhereParentIdSql(false, ""); + deleteByParentIdInSql = delStmt + deriveWhereParentIdSql(true, ""); + if (many.isElementCollection()) { + elementCollectionInsertSql = elementCollectionInsert(); + } else { + elementCollectionInsertSql = null; + } + } + + private String elementCollectionInsert() { + + StringBuilder sb = new StringBuilder(200); + sb.append("insert into ").append(many.targetTable()).append(" ("); + append(sb, "", ",", ""); + + Cols cols = new Cols(sb); + VisitAllUsing.visitOne(many.targetDescriptor, cols); + sb.append(") values ("); + appendBind(sb, exportedProperties.length, true); + appendBind(sb, cols.colCount, false); + sb.append(")"); + + return sb.toString(); + } + + public String insertElementCollection() { + return elementCollectionInsertSql; + } + + private static class Cols extends BaseTablePropertyVisitor { + + int colCount; + + private final StringBuilder sb; + + private Cols(StringBuilder sb) { + this.sb = sb; + } + + @Override + public void visitEmbeddedScalar(BeanProperty p, BeanPropertyAssocOne embedded) { + sb.append(",").append(p.getDbColumn()); + colCount++; + } + + @Override + public void visitOneImported(BeanPropertyAssocOne p) { + + } + + @Override + public void visitScalar(BeanProperty p) { + sb.append(",").append(p.getDbColumn()); + colCount++; + } + + @Override + public void visitEnd() { + + } + } + + String lazyFetchOrderBy(String fetchOrderBy) { + + // derive lazyFetchOrderBy + StringBuilder sb = new StringBuilder(50); + for (int i = 0; i < exportedProperties.length; i++) { + if (i > 0) { + sb.append(", "); + } + // these fk columns are either on the intersection (int_) or base table (t0) + String fkTableAlias = hasJoinTable ? "int_" : "t0"; + sb.append(fkTableAlias).append(".").append(exportedProperties[i].getForeignDbColumn()); + } + sb.append(", ").append(fetchOrderBy); + return sb.toString().trim(); + } + + /** + * Add a where clause to the query for a given list of parent Id's. + */ + void addWhereParentIdIn(SpiQuery query, List parentIds) { + + String tableAlias = hasJoinTable ? "int_." : "t0."; + if (hasJoinTable) { + query.setM2MIncludeJoin(many.inverseJoin); + } + String rawWhere = deriveWhereParentIdSql(true, tableAlias); + String expr = descriptor.getParentIdInExpr(parentIds.size(), rawWhere); + + many.bindParentIdsIn(expr, parentIds, query); + } + + List findIdsByParentId(Object parentId, Transaction t, List excludeDetailIds) { + + String rawWhere = deriveWhereParentIdSql(false, ""); + + SpiEbeanServer server = descriptor.getEbeanServer(); + SpiQuery q = many.newQuery(server); + many.bindParentIdEq(rawWhere, parentId, q); + + if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) { + q.where().not(q.getExpressionFactory().idIn(excludeDetailIds)); + } + + return server.findIds(q, t); + } + + List findIdsByParentIdList(List parentIds, Transaction t, List excludeDetailIds) { + + String rawWhere = deriveWhereParentIdSql(true, ""); + String inClause = buildInClauseBinding(parentIds.size(), exportedPropertyBindProto); + + String expr = rawWhere + inClause; + + SpiEbeanServer server = descriptor.getEbeanServer(); + SpiQuery q = many.newQuery(server); + //Query q = server.find(propertyType); + many.bindParentIdsIn(expr, parentIds, q); + + if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) { + q.where().not(q.getExpressionFactory().idIn(excludeDetailIds)); + } + + return server.findIds(q, t); + } + + SqlUpdate deleteByParentId(Object parentId) { + DefaultSqlUpdate sqlDelete = new DefaultSqlUpdate(deleteByParentIdSql); + many.bindParentId(sqlDelete, parentId); + return sqlDelete; + } + + SqlUpdate deleteByParentIdList(List parentIds) { + + StringBuilder sb = new StringBuilder(100); + sb.append(deleteByParentIdInSql); + sb.append(buildInClauseBinding(parentIds.size(), exportedPropertyBindProto)); + + DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString()); + many.bindParentIds(delete, parentIds); + return delete; + } + + private void appendBind(StringBuilder sb, int count, boolean skipComma) { + for (int i = 0; i < count; i++) { + if (!skipComma || i > 0) { + sb.append(","); + } + sb.append("?"); + } + } + + private void append(StringBuilder sb, String tableAlias, String prefix, String suffix) { + for (int i = 0; i < exportedProperties.length; i++) { + String fkColumn = exportedProperties[i].getForeignDbColumn(); + if (i > 0) { + sb.append(prefix); + } + sb.append(tableAlias).append(fkColumn); + sb.append(suffix); + } + } + private String deriveWhereParentIdSql(boolean inClause, String tableAlias) { + + StringBuilder sb = new StringBuilder(); + + if (inClause) { + sb.append("("); + } + for (int i = 0; i < exportedProperties.length; i++) { + String fkColumn = exportedProperties[i].getForeignDbColumn(); + if (i > 0) { + String s = inClause ? "," : " and "; + sb.append(s); + } + sb.append(tableAlias).append(fkColumn); + if (!inClause) { + sb.append("=? "); + } + } + if (inClause) { + sb.append(")"); + } + return sb.toString(); + } + + private String buildInClauseBinding(int size, String bindProto) { + + if (descriptor.isSimpleId()) { + return descriptor.getIdBinder().getIdInValueExpr(false, size); + } + StringBuilder sb = new StringBuilder(10 + (size * (bindProto.length() + 1))); + sb.append(" in"); + sb.append(" ("); + for (int i = 0; i < size; i++) { + if (i > 0) { + sb.append(","); + } + sb.append(bindProto); + } + sb.append(") "); + return sb.toString(); + } + + private String deriveExportedPropertyBindProto() { + if (exportedProperties.length == 1) { + return "?"; + } + StringBuilder sb = new StringBuilder(); + sb.append("("); + for (int i = 0; i < exportedProperties.length; i++) { + if (i > 0) { + sb.append(","); + } + sb.append("?"); + } + sb.append(")"); + return sb.toString(); + } + +} diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java index 2d34242b1..c4f28fd60 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java @@ -16,10 +16,12 @@ import io.ebeaninternal.server.deploy.id.ImportedId; import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne; import io.ebeaninternal.server.el.ElPropertyChainBuilder; import io.ebeaninternal.server.el.ElPropertyValue; +import io.ebeaninternal.server.query.STreePropertyAssocOne; import io.ebeaninternal.server.query.SqlBeanLoad; import io.ebeaninternal.server.query.SqlJoinType; import io.ebeaninternal.server.text.json.ReadJson; import io.ebeaninternal.server.text.json.SpiJsonWriter; +import io.ebeaninternal.server.type.ScalarType; import javax.persistence.PersistenceException; import java.io.IOException; @@ -32,7 +34,7 @@ import java.util.Map; /** * Property mapped to a joined bean. */ -public class BeanPropertyAssocOne extends BeanPropertyAssoc { +public class BeanPropertyAssocOne extends BeanPropertyAssoc implements STreePropertyAssocOne { private final boolean oneToOne; @@ -94,8 +96,12 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc { } @Override - public void initialise() { - super.initialise(); + public void initialise(BeanDescriptorInitContext initContext) { + super.initialise(initContext); + initialiseAssocOne(); + } + + private void initialiseAssocOne() { localHelp = createHelp(embedded, oneToOneExported); if (!isTransient) { @@ -417,6 +423,11 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc { } } + @Override + public ScalarType getIdScalarType() { + return targetDescriptor.getIdProperty().getScalarType(); + } + /** * Return the Id values from the given bean. */ @@ -539,24 +550,7 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc { */ private ExportedProperty findMatch(boolean embeddedProp, BeanProperty prop) { - String matchColumn = prop.getDbColumn(); - - String searchTable = tableJoin.getTable(); - TableJoinColumn[] columns = tableJoin.columns(); - - for (TableJoinColumn column : columns) { - String matchTo = column.getLocalDbColumn(); - - if (matchColumn.equalsIgnoreCase(matchTo)) { - String foreignCol = column.getForeignDbColumn(); - return new ExportedProperty(embeddedProp, foreignCol, prop); - } - } - - String msg = "Error with the Join on [" + getFullBeanName() - + "]. Could not find the matching foreign key for [" + matchColumn + "] in table[" + searchTable + "]?" - + " Perhaps using a @JoinColumn with the name/referencedColumnName attributes swapped?"; - throw new PersistenceException(msg); + return findMatch(embeddedProp, prop, prop.getDbColumn(), tableJoin); } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertySimpleCollection.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertySimpleCollection.java index c3d7fc6a2..69f79bd1c 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertySimpleCollection.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertySimpleCollection.java @@ -4,8 +4,27 @@ import io.ebeaninternal.server.deploy.meta.DeployBeanPropertySimpleCollection; public class BeanPropertySimpleCollection extends BeanPropertyAssocMany { + private BeanDescriptor elementDescriptor; + public BeanPropertySimpleCollection(BeanDescriptor descriptor, DeployBeanPropertySimpleCollection deploy) { super(descriptor, deploy); + this.elementDescriptor = deploy.getElementDescriptor(); } + @Override + public void initialise(BeanDescriptorInitContext initContext) { + super.initialise(initContext); + if (isElementCollection()) { + // initialise all non-id properties (we don't have an Id property) + elementDescriptor.initialiseOther(initContext); + } + } + + void initialiseTargetDescriptor(BeanDescriptorInitContext initContext) { + if (isElementCollection()) { + targetDescriptor = elementDescriptor; + } else { + targetDescriptor = descriptor.getBeanDescriptor(targetType); + } + } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanSetHelp.java b/src/main/java/io/ebeaninternal/server/deploy/BeanSetHelp.java index 0dfdf40af..450ed659a 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanSetHelp.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanSetHelp.java @@ -5,7 +5,6 @@ import io.ebean.Query; import io.ebean.Transaction; import io.ebean.bean.BeanCollection; import io.ebean.bean.BeanCollectionAdd; -import io.ebean.bean.BeanCollectionLoader; import io.ebean.bean.EntityBean; import io.ebean.common.BeanSet; import io.ebeaninternal.server.text.json.SpiJsonWriter; @@ -19,32 +18,18 @@ import java.util.Set; */ public final class BeanSetHelp extends BaseCollectionHelp { - private final BeanPropertyAssocMany many; - private final BeanDescriptor targetDescriptor; - private final String propertyName; - private BeanCollectionLoader loader; - /** * When attached to a specific many property. */ - public BeanSetHelp(BeanPropertyAssocMany many) { - this.many = many; - this.targetDescriptor = many.getTargetDescriptor(); - this.propertyName = many.getName(); + BeanSetHelp(BeanPropertyAssocMany many) { + super(many); } /** * For a query that returns a set. */ - public BeanSetHelp() { - this.many = null; - this.targetDescriptor = null; - this.propertyName = null; - } - - @Override - public void setLoader(BeanCollectionLoader loader) { - this.loader = loader; + BeanSetHelp() { + super(); } @Override @@ -61,15 +46,6 @@ public final class BeanSetHelp extends BaseCollectionHelp { } } - @Override - public void add(BeanCollection collection, EntityBean bean, boolean withCheck) { - if (withCheck) { - collection.internalAddWithCheck(bean); - } else { - collection.internalAdd(bean); - } - } - @Override public BeanCollection createEmptyNoParent() { return new BeanSet<>(); @@ -142,13 +118,6 @@ public final class BeanSetHelp extends BaseCollectionHelp { } else { set = (Set) collection; } - - if (!set.isEmpty() || ctx.isIncludeEmpty()) { - ctx.beginAssocMany(name); - for (Object bean : set) { - targetDescriptor.jsonWrite(ctx, (EntityBean) bean); - } - ctx.endAssocMany(); - } + jsonWriteCollection(ctx, name, set); } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanTable.java b/src/main/java/io/ebeaninternal/server/deploy/BeanTable.java index c481660e8..f2cc09e78 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanTable.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanTable.java @@ -42,6 +42,16 @@ public class BeanTable { this.idProperty = mutable.createIdProperty(owner); } + /** + * Construct for element collection. + */ + public BeanTable(BeanDescriptorMap owner, String tableName, Class beanType) { + this.owner = owner; + this.beanType = beanType; + this.baseTable = tableName; + this.idProperty = null; + } + @Override public String toString() { return baseTable; diff --git a/src/main/java/io/ebeaninternal/server/deploy/DbReadContext.java b/src/main/java/io/ebeaninternal/server/deploy/DbReadContext.java index b7f346313..c282eca47 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/DbReadContext.java +++ b/src/main/java/io/ebeaninternal/server/deploy/DbReadContext.java @@ -5,6 +5,7 @@ import io.ebean.bean.EntityBean; import io.ebean.bean.EntityBeanIntercept; import io.ebean.bean.PersistenceContext; import io.ebeaninternal.api.SpiQuery; +import io.ebeaninternal.server.query.STreePropertyAssocMany; import io.ebeaninternal.server.type.DataReader; import java.util.Map; @@ -68,7 +69,7 @@ public interface DbReadContext { * Return the property that is associated with the many. There can only be * one. This can be null. */ - BeanPropertyAssocMany getManyProperty(); + STreePropertyAssocMany getManyProperty(); /** * Set back the bean that has just been loaded with its id. diff --git a/src/main/java/io/ebeaninternal/server/deploy/DynamicPropertyBase.java b/src/main/java/io/ebeaninternal/server/deploy/DynamicPropertyBase.java index e0fae9d19..194f4eb47 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/DynamicPropertyBase.java +++ b/src/main/java/io/ebeaninternal/server/deploy/DynamicPropertyBase.java @@ -1,7 +1,7 @@ package io.ebeaninternal.server.deploy; +import io.ebeaninternal.server.query.STreeProperty; import io.ebeaninternal.server.query.SqlJoinType; -import io.ebeaninternal.server.query.SqlTreeProperty; import io.ebeaninternal.server.type.ScalarType; import java.util.List; @@ -9,14 +9,14 @@ import java.util.List; /** * Abstract base for dynamic properties. */ -abstract class DynamicPropertyBase implements SqlTreeProperty { +abstract class DynamicPropertyBase implements STreeProperty { final String name; final String fullName; final String elPrefix; final ScalarType scalarType; - public DynamicPropertyBase(String name, String fullName, String elPrefix, ScalarType scalarType) { + DynamicPropertyBase(String name, String fullName, String elPrefix, ScalarType scalarType) { this.name = name; this.fullName = fullName; this.elPrefix = elPrefix; @@ -43,6 +43,11 @@ abstract class DynamicPropertyBase implements SqlTreeProperty { return false; } + @Override + public boolean isFormula() { + return false; + } + @Override public String getElPrefix() { return elPrefix; @@ -67,4 +72,9 @@ abstract class DynamicPropertyBase implements SqlTreeProperty { public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) { // do not add to from usually } + + @Override + public String getEncryptKeyAsString() { + return null; + } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/ElementEntityBean.java b/src/main/java/io/ebeaninternal/server/deploy/ElementEntityBean.java new file mode 100644 index 000000000..3724cac99 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/deploy/ElementEntityBean.java @@ -0,0 +1,81 @@ +package io.ebeaninternal.server.deploy; + +import io.ebean.bean.EntityBean; +import io.ebean.bean.EntityBeanIntercept; + +class ElementEntityBean implements EntityBean { + + private final String[] properties; + + private Object[] data; + + private EntityBeanIntercept intercept; + + ElementEntityBean(String[] properties) { + this.properties = properties; + this.intercept = new EntityBeanIntercept(this); + } + + @Override + public String[] _ebean_getPropertyNames() { + return properties; + } + + @Override + public String _ebean_getPropertyName(int pos) { + return properties[pos]; + } + + @Override + public String _ebean_getMarker() { + return null; + } + + @Override + public Object _ebean_newInstance() { + return new ElementEntityBean(properties); + } + + @Override + public void _ebean_setEmbeddedLoaded() { + + } + + @Override + public boolean _ebean_isEmbeddedNewOrDirty() { + return false; + } + + @Override + public EntityBeanIntercept _ebean_getIntercept() { + return intercept; + } + + @Override + public EntityBeanIntercept _ebean_intercept() { + return intercept; + } + + @Override + public void _ebean_setField(int fieldIndex, Object value) { + if (data == null) { + data = new Object[properties.length]; + } + data[fieldIndex] = value; + } + + @Override + public void _ebean_setFieldIntercept(int fieldIndex, Object value) { + _ebean_setField(fieldIndex, value); + } + + @Override + public Object _ebean_getField(int fieldIndex) { + return data == null ? null : data[fieldIndex]; + } + + @Override + public Object _ebean_getFieldIntercept(int fieldIndex) { + return _ebean_getField(fieldIndex); + } +} diff --git a/src/main/java/io/ebeaninternal/server/deploy/FormulaPropertyPath.java b/src/main/java/io/ebeaninternal/server/deploy/FormulaPropertyPath.java index 817868cd5..c63d39b3e 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/FormulaPropertyPath.java +++ b/src/main/java/io/ebeaninternal/server/deploy/FormulaPropertyPath.java @@ -1,7 +1,7 @@ package io.ebeaninternal.server.deploy; import io.ebeaninternal.server.el.ElPropertyDeploy; -import io.ebeaninternal.server.query.SqlTreeProperty; +import io.ebeaninternal.server.query.STreeProperty; import io.ebeaninternal.server.type.ScalarType; import java.sql.Types; @@ -89,7 +89,7 @@ class FormulaPropertyPath { return alias; } - SqlTreeProperty build() { + STreeProperty build() { DeployPropertyParser parser = descriptor.parser().setCatchFirst(true); diff --git a/src/main/java/io/ebeaninternal/server/deploy/InheritInfo.java b/src/main/java/io/ebeaninternal/server/deploy/InheritInfo.java index 0e045c4e2..d93d74fdc 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/InheritInfo.java +++ b/src/main/java/io/ebeaninternal/server/deploy/InheritInfo.java @@ -184,7 +184,7 @@ public class InheritInfo { for (InheritInfo childInfo : children) { // recursively search this child bean descriptor - prop = childInfo.desc().findBeanProperty(propertyName); + prop = childInfo.desc().findProperty(propertyName); if (prop != null) { return prop; } diff --git a/src/main/java/io/ebeaninternal/server/deploy/id/IdBinder.java b/src/main/java/io/ebeaninternal/server/deploy/id/IdBinder.java index 766f9eb60..d3a018e10 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/id/IdBinder.java +++ b/src/main/java/io/ebeaninternal/server/deploy/id/IdBinder.java @@ -6,6 +6,7 @@ import io.ebeaninternal.server.core.DefaultSqlUpdate; import io.ebeaninternal.server.deploy.BeanProperty; import io.ebeaninternal.server.deploy.DbReadContext; import io.ebeaninternal.server.deploy.DbSqlContext; +import io.ebeaninternal.server.query.STreeProperty; import io.ebeaninternal.server.type.DataBind; import java.io.DataInput; @@ -52,7 +53,7 @@ public interface IdBinder { /** * Return the Id BeanProperty. */ - BeanProperty getBeanProperty(); + STreeProperty getBeanProperty(); /** * Find a BeanProperty that is mapped to the database column. diff --git a/src/main/java/io/ebeaninternal/server/deploy/meta/BeanPropertyElementSetter.java b/src/main/java/io/ebeaninternal/server/deploy/meta/BeanPropertyElementSetter.java new file mode 100644 index 000000000..f20ee47cf --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/deploy/meta/BeanPropertyElementSetter.java @@ -0,0 +1,26 @@ +package io.ebeaninternal.server.deploy.meta; + +import io.ebean.bean.EntityBean; +import io.ebeaninternal.server.properties.BeanPropertySetter; + +/** + * Setter used for "element beans" with ElementCollection. + */ +class BeanPropertyElementSetter implements BeanPropertySetter { + + private final int pos; + + BeanPropertyElementSetter(int pos) { + this.pos = pos; + } + + @Override + public void set(EntityBean bean, Object value) { + bean._ebean_setField(pos, value); + } + + @Override + public void setIntercept(EntityBean bean, Object value) { + set(bean, value); + } +} diff --git a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java index ce091b2ec..f2ae582ee 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java +++ b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java @@ -210,6 +210,8 @@ public class DeployBeanProperty { private boolean undirectionalShadow; + private boolean elementProperty; + private int sortOrder; private boolean excludedFromHistory; @@ -444,7 +446,11 @@ public class DeployBeanProperty { } public BeanPropertySetter getSetter() { - return setter; + if (elementProperty) { + return new BeanPropertyElementSetter(sortOrder); + } else { + return setter; + } } /** @@ -1081,4 +1087,10 @@ public class DeployBeanProperty { return dbMigrationInfos; } + /** + * Set when this property is part of a 'element bean' used with ElementCollection. + */ + public void setElementProperty() { + this.elementProperty = true; + } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocMany.java b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocMany.java index 112223110..4e6d7d7d8 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocMany.java +++ b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocMany.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.deploy.meta; import io.ebean.bean.BeanCollection.ModifyListenMode; +import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeaninternal.server.deploy.ManyType; import io.ebeaninternal.server.deploy.TableJoin; @@ -23,6 +24,8 @@ public class DeployBeanPropertyAssocMany extends DeployBeanPropertyAssoc { private boolean o2mJoinTable; + private boolean elementCollection; + /** * Flag to indicate this is a unidirectional relationship. */ @@ -46,6 +49,11 @@ public class DeployBeanPropertyAssocMany extends DeployBeanPropertyAssoc { private DeployOrderColumn orderColumn; + /** + * Effectively the dynamically created target descriptor (that doesn't have a mapped type/class per say). + */ + private BeanDescriptor elementDescriptor; + /** * Create this property. */ @@ -231,5 +239,24 @@ public class DeployBeanPropertyAssocMany extends DeployBeanPropertyAssoc { this.o2mJoinTable = true; setModifyListenMode(ModifyListenMode.ALL); } + + public void setElementCollection() { + elementCollection = true; + cascadeInfo.setSaveDelete(true, true); + setModifyListenMode(ModifyListenMode.ALL); + } + + public boolean isElementCollection() { + return elementCollection; + } + + public void setElementDescriptor(BeanDescriptor elementDescriptor) { + this.elementDescriptor = elementDescriptor; + } + + @SuppressWarnings("unchecked") + public BeanDescriptor getElementDescriptor() { + return (BeanDescriptor)elementDescriptor; + } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocOne.java b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocOne.java index ed26cc045..920d0141e 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocOne.java +++ b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocOne.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.deploy.meta; import io.ebeaninternal.server.deploy.PropertyForeignKey; +import io.ebeaninternal.server.query.SqlJoinType; import javax.persistence.CascadeType; @@ -162,4 +163,12 @@ public class DeployBeanPropertyAssocOne extends DeployBeanPropertyAssoc { public boolean isOrphanRemoval() { return orphanRemoval; } + + public void setJoinType(boolean outerJoin) { + tableJoin.setType(outerJoin ? SqlJoinType.OUTER : SqlJoinType.INNER); + } + + public void setJoinColumns(DeployTableJoinColumn[] columns, boolean reverse) { + tableJoin.setColumns(columns, reverse); + } } 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 dce5962c1..460901474 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java @@ -7,18 +7,24 @@ import io.ebean.annotation.Where; import io.ebean.bean.BeanCollection.ModifyListenMode; import io.ebean.config.NamingConvention; import io.ebean.config.TableName; +import io.ebean.util.CamelCaseHelper; import io.ebean.util.StringHelper; import io.ebeaninternal.server.deploy.BeanDescriptorManager; import io.ebeaninternal.server.deploy.BeanProperty; import io.ebeaninternal.server.deploy.BeanTable; +import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; import io.ebeaninternal.server.deploy.meta.DeployBeanProperty; import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany; import io.ebeaninternal.server.deploy.meta.DeployOrderColumn; import io.ebeaninternal.server.deploy.meta.DeployTableJoin; import io.ebeaninternal.server.deploy.meta.DeployTableJoinColumn; import io.ebeaninternal.server.query.SqlJoinType; +import io.ebeaninternal.server.type.ScalarType; import javax.persistence.CascadeType; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; @@ -38,8 +44,8 @@ class AnnotationAssocManys extends AnnotationParser { /** * Create with the DeployInfo. */ - AnnotationAssocManys(DeployBeanInfo info, boolean javaxValidationAnnotations, BeanDescriptorManager factory) { - super(info, javaxValidationAnnotations); + AnnotationAssocManys(DeployBeanInfo info, ReadAnnotationConfig readConfig, BeanDescriptorManager factory) { + super(info, readConfig); this.factory = factory; } @@ -82,6 +88,10 @@ class AnnotationAssocManys extends AnnotationParser { if (manyToMany != null) { readToMany(manyToMany, prop); } + ElementCollection elementCollection = get(prop, ElementCollection.class); + if (elementCollection != null) { + readElementCollection(prop, elementCollection); + } if (get(prop, HistoryExclude.class) != null) { prop.setExcludedFromHistory(); @@ -160,6 +170,59 @@ class AnnotationAssocManys extends AnnotationParser { } } + private void readElementCollection(DeployBeanPropertyAssocMany prop, ElementCollection elementCollection) { + + prop.setElementCollection(); + if (!elementCollection.targetClass().equals(void.class)) { + prop.setTargetType(elementCollection.targetClass()); + } + Column column = get(prop, Column.class); + if (column != null) { + prop.setDbColumn(column.name()); + prop.setDbLength(column.length()); + prop.setDbScale(column.scale()); + } + + CollectionTable collectionTable = get(prop, CollectionTable.class); + + String fullTableName = getFullTableName(collectionTable); + if (fullTableName == null) { + fullTableName = descriptor.getBaseTable()+"_"+ CamelCaseHelper.toUnderscoreFromCamel(prop.getName()); + } + //namingConvention. + BeanTable beanTable = factory.getCollectionBeanTable(fullTableName, prop.getTargetType()); + prop.setBeanTable(beanTable); + + if (collectionTable != null) { + prop.getTableJoin().addJoinColumn(true, collectionTable.joinColumns(), beanTable); + } + + Class elementType = prop.getTargetType(); + + DeployBeanDescriptor elementDescriptor = factory.createDeployDescriptor(elementType); + elementDescriptor.setBaseTable(new TableName(fullTableName), readConfig.getAsOfViewSuffix(), readConfig.getVersionsBetweenSuffix()); + + ScalarType scalarType = util.getTypeManager().getScalarType(elementType); + DeployBeanProperty elementProp = new DeployBeanProperty(elementDescriptor, elementType, scalarType, null); + + elementProp.setName("value"); + elementProp.setDbColumn(prop.getDbColumn()); + elementProp.setNullable(false); + elementProp.setDbInsertable(true); + elementProp.setDbUpdateable(true); + elementProp.setDbRead(true); + elementProp.setElementProperty(); + + elementDescriptor.addBeanProperty(elementProp); + elementDescriptor.setProperties(new String[]{"value"}); + elementDescriptor.setName(prop.getFullBeanName()); + + Class owningType = prop.getOwningType(); + + factory.createUnidirectional(elementDescriptor, owningType, beanTable, prop.getTableJoin()); + prop.setElementDescriptor(factory.createElementDescriptor(elementDescriptor)); + } + /** * Define the joins for a ManyToMany relationship. *

@@ -204,6 +267,24 @@ class AnnotationAssocManys extends AnnotationParser { return sb.toString(); } + /** + * Return the full table name + */ + private String getFullTableName(CollectionTable collectionTable) { + if (collectionTable == null) { + return null; + } + StringBuilder sb = new StringBuilder(); + if (!StringHelper.isNull(collectionTable.catalog())) { + sb.append(collectionTable.catalog()).append("."); + } + if (!StringHelper.isNull(collectionTable.schema())) { + sb.append(collectionTable.schema()).append("."); + } + sb.append(collectionTable.name()); + return sb.toString(); + } + /** * Define intersection table and foreign key columns for ManyToMany. *

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 cd53cd350..4be0a3ad1 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java @@ -41,8 +41,8 @@ public class AnnotationAssocOnes extends AnnotationParser { /** * Create with the deploy Info. */ - AnnotationAssocOnes(DeployBeanInfo info, boolean javaxValidationAnnotations, BeanDescriptorManager factory) { - super(info, javaxValidationAnnotations); + AnnotationAssocOnes(DeployBeanInfo info, ReadAnnotationConfig readConfig, BeanDescriptorManager factory) { + super(info, readConfig); this.factory = factory; } @@ -152,7 +152,7 @@ public class AnnotationAssocOnes extends AnnotationParser { } } - info.setBeanJoinType(prop, prop.isNullable()); + prop.setJoinType(prop.isNullable()); if (!prop.getTableJoin().hasJoinColumns() && beanTable != null) { diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java index c5cb0b810..208b0bdf3 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java @@ -42,25 +42,15 @@ public class AnnotationClass extends AnnotationParser { private final boolean disableL2Cache; - /** - * Create for normal early parse of class level annotations. - */ - public AnnotationClass(DeployBeanInfo info, boolean validationAnnotations, String asOfViewSuffix, String versionsBetweenSuffix, boolean disableL2Cache) { - super(info, validationAnnotations); - this.asOfViewSuffix = asOfViewSuffix; - this.versionsBetweenSuffix = versionsBetweenSuffix; - this.disableL2Cache = disableL2Cache; - } - /** * Create to parse AttributeOverride annotations which is run last * after all the properties/fields have been parsed fully. */ - public AnnotationClass(DeployBeanInfo info) { - super(info, false); - this.asOfViewSuffix = null; - this.versionsBetweenSuffix = null; - this.disableL2Cache = false; + public AnnotationClass(DeployBeanInfo info, ReadAnnotationConfig readConfig) { + super(info, readConfig); + this.asOfViewSuffix = readConfig.getAsOfViewSuffix(); + this.versionsBetweenSuffix = readConfig.getVersionsBetweenSuffix(); + this.disableL2Cache = readConfig.isDisableL2Cache(); } /** diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java index a84af1f0c..4be082b93 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java @@ -91,14 +91,11 @@ public class AnnotationFields extends AnnotationParser { */ private FetchType defaultLobFetchType = FetchType.LAZY; - AnnotationFields(GeneratedPropertyFactory generatedPropFactory, DeployBeanInfo info, - boolean javaxValidationAnnotations, boolean jacksonAnnotationsPresent, boolean eagerFetchLobs) { - - super(info, javaxValidationAnnotations); - this.jacksonAnnotationsPresent = jacksonAnnotationsPresent; - this.generatedPropFactory = generatedPropFactory; - - if (eagerFetchLobs) { + AnnotationFields(DeployBeanInfo info, ReadAnnotationConfig readConfig) { + super(info, readConfig); + this.jacksonAnnotationsPresent = readConfig.isJacksonAnnotations(); + this.generatedPropFactory = readConfig.getGeneratedPropFactory(); + if (readConfig.isEagerFetchLobs()) { defaultLobFetchType = FetchType.EAGER; } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationParser.java b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationParser.java index 034caa595..1bcb1faf3 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationParser.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationParser.java @@ -27,9 +27,12 @@ public abstract class AnnotationParser extends AnnotationBase { protected final boolean validationAnnotations; - public AnnotationParser(DeployBeanInfo info, boolean validationAnnotations) { + protected final ReadAnnotationConfig readConfig; + + public AnnotationParser(DeployBeanInfo info, ReadAnnotationConfig readConfig) { super(info.getUtil()); - this.validationAnnotations = validationAnnotations; + this.readConfig = readConfig; + this.validationAnnotations = readConfig.isJavaxValidationAnnotations(); this.info = info; this.beanType = info.getDescriptor().getBeanType(); this.descriptor = info.getDescriptor(); diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationSql.java b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationSql.java index d488e933c..df9622b02 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationSql.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationSql.java @@ -7,10 +7,10 @@ import io.ebeaninternal.server.deploy.BeanDescriptor; /** * Read the class level deployment annotations. */ -public class AnnotationSql extends AnnotationParser { +class AnnotationSql extends AnnotationParser { - public AnnotationSql(DeployBeanInfo info, boolean javaxValidationAnnotations) { - super(info, javaxValidationAnnotations); + AnnotationSql(DeployBeanInfo info, ReadAnnotationConfig readConfig) { + super(info, readConfig); } @Override diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/DeployBeanInfo.java b/src/main/java/io/ebeaninternal/server/deploy/parse/DeployBeanInfo.java index 553e263c8..d683578bc 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/DeployBeanInfo.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/DeployBeanInfo.java @@ -3,7 +3,6 @@ package io.ebeaninternal.server.deploy.parse; import io.ebean.RawSql; import io.ebeaninternal.server.deploy.TableJoin; import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; -import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne; import io.ebeaninternal.server.deploy.meta.DeployTableJoin; import io.ebeaninternal.server.query.SqlJoinType; import io.ebeaninternal.server.rawsql.SpiRawSql; @@ -70,15 +69,6 @@ public class DeployBeanInfo { return tableJoin; } - /** - * Set a the join alias for a assoc one property. - */ - public void setBeanJoinType(DeployBeanPropertyAssocOne beanProp, boolean outerJoin) { - - DeployTableJoin tableJoin = beanProp.getTableJoin(); - tableJoin.setType(outerJoin ? SqlJoinType.OUTER : SqlJoinType.INNER); - } - /** * Add named RawSql from ebean.xml. */ diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/ReadAnnotationConfig.java b/src/main/java/io/ebeaninternal/server/deploy/parse/ReadAnnotationConfig.java new file mode 100644 index 000000000..4b3bf96d0 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/ReadAnnotationConfig.java @@ -0,0 +1,58 @@ +package io.ebeaninternal.server.deploy.parse; + +import io.ebean.config.ServerConfig; +import io.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory; + +/** + * Configuration used when reading the deployment annotations. + */ +class ReadAnnotationConfig { + + private final GeneratedPropertyFactory generatedPropFactory; + private final String asOfViewSuffix; + private final String versionsBetweenSuffix; + private final boolean disableL2Cache; + private final boolean eagerFetchLobs; + private final boolean javaxValidationAnnotations; + private final boolean jacksonAnnotations; + + ReadAnnotationConfig(GeneratedPropertyFactory generatedPropFactory, String asOfViewSuffix, String versionsBetweenSuffix, ServerConfig serverConfig) { + + this.generatedPropFactory = generatedPropFactory; + this.asOfViewSuffix = asOfViewSuffix; + this.versionsBetweenSuffix = versionsBetweenSuffix; + this.disableL2Cache = serverConfig.isDisableL2Cache(); + this.eagerFetchLobs = serverConfig.isEagerFetchLobs(); + + this.javaxValidationAnnotations = generatedPropFactory.getClassLoadConfig().isJavaxValidationAnnotationsPresent(); + this.jacksonAnnotations = generatedPropFactory.getClassLoadConfig().isJacksonAnnotationsPresent(); + } + + GeneratedPropertyFactory getGeneratedPropFactory() { + return generatedPropFactory; + } + + String getAsOfViewSuffix() { + return asOfViewSuffix; + } + + String getVersionsBetweenSuffix() { + return versionsBetweenSuffix; + } + + boolean isDisableL2Cache() { + return disableL2Cache; + } + + boolean isEagerFetchLobs() { + return eagerFetchLobs; + } + + boolean isJavaxValidationAnnotations() { + return javaxValidationAnnotations; + } + + boolean isJacksonAnnotations() { + return jacksonAnnotations; + } +} diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/ReadAnnotations.java b/src/main/java/io/ebeaninternal/server/deploy/parse/ReadAnnotations.java index 6af425828..534e9cb82 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/ReadAnnotations.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/ReadAnnotations.java @@ -1,5 +1,6 @@ package io.ebeaninternal.server.deploy.parse; +import io.ebean.config.ServerConfig; import io.ebeaninternal.server.deploy.BeanDescriptorManager; import io.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory; @@ -9,38 +10,10 @@ import io.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory */ public class ReadAnnotations { - /** - * Creates appropriate generated properties - WhenXXX, WhoXXX, Version etc. - */ - private final GeneratedPropertyFactory generatedPropFactory; + private final ReadAnnotationConfig readConfig; - /** - * Typically _with_history and when appended to the base table derives the name of - * the view that unions the base table with the history table to support asOf queries. - */ - private final String asOfViewSuffix; - - private final String versionsBetweenSuffix; - - /** - * True if the javax validation annotations are present in the classpath. - */ - private final boolean javaxValidationAnnotations; - - /** - * True if the jackson annotations are present in the classpath. - */ - private final boolean jacksonAnnotations; - - private final boolean disableL2Cache; - - public ReadAnnotations(GeneratedPropertyFactory generatedPropFactory, String asOfViewSuffix, String versionsBetweenSuffix, boolean disableL2Cache) { - this.generatedPropFactory = generatedPropFactory; - this.asOfViewSuffix = asOfViewSuffix; - this.versionsBetweenSuffix = versionsBetweenSuffix; - this.javaxValidationAnnotations = generatedPropFactory.getClassLoadConfig().isJavaxValidationAnnotationsPresent(); - this.jacksonAnnotations = generatedPropFactory.getClassLoadConfig().isJacksonAnnotationsPresent(); - this.disableL2Cache = disableL2Cache; + public ReadAnnotations(GeneratedPropertyFactory generatedPropFactory, String asOfViewSuffix, String versionsBetweenSuffix, ServerConfig serverConfig) { + this.readConfig = new ReadAnnotationConfig(generatedPropFactory, asOfViewSuffix, versionsBetweenSuffix, serverConfig); } /** @@ -50,12 +23,10 @@ public class ReadAnnotations { * to resolve the relationships etc. *

*/ - public void readInitial(DeployBeanInfo info, boolean eagerFetchLobs) { - + public void readInitial(DeployBeanInfo info) { try { - new AnnotationClass(info, javaxValidationAnnotations, asOfViewSuffix, versionsBetweenSuffix, disableL2Cache).parse(); - new AnnotationFields(generatedPropFactory, info, javaxValidationAnnotations, jacksonAnnotations, eagerFetchLobs).parse(); - + new AnnotationClass(info, readConfig).parse(); + new AnnotationFields(info, readConfig).parse(); } catch (RuntimeException e) { throw new RuntimeException("Error reading annotations for " + info, e); } @@ -75,14 +46,14 @@ public class ReadAnnotations { try { - new AnnotationAssocOnes(info, javaxValidationAnnotations, factory).parse(); - new AnnotationAssocManys(info, javaxValidationAnnotations, factory).parse(); + new AnnotationAssocOnes(info, readConfig, factory).parse(); + new AnnotationAssocManys(info, readConfig, factory).parse(); // read the Sql annotations last because they may be // dependent on field level annotations - new AnnotationSql(info, javaxValidationAnnotations).parse(); + new AnnotationSql(info, readConfig).parse(); - new AnnotationClass(info).parseAttributeOverride(); + new AnnotationClass(info, readConfig).parseAttributeOverride(); info.getDescriptor().postAnnotations(); } catch (RuntimeException e) { diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/TransientProperties.java b/src/main/java/io/ebeaninternal/server/deploy/parse/TransientProperties.java index 4be75b3b3..e7896b96a 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/TransientProperties.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/TransientProperties.java @@ -5,8 +5,6 @@ import io.ebeaninternal.server.deploy.meta.DeployBeanProperty; import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany; import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne; -import java.util.List; - /** * Mark transient properties. */ @@ -20,29 +18,22 @@ public class TransientProperties { */ public void process(DeployBeanDescriptor desc) { - List props = desc.propertiesBase(); - for (DeployBeanProperty prop : props) { + for (DeployBeanProperty prop : desc.propertiesBase()) { if (!prop.isDbRead() && !prop.isDbInsertable() && !prop.isDbUpdateable()) { - // non-transient... prop.setTransient(); } } - List> ones = desc.propertiesAssocOne(); - for (DeployBeanPropertyAssocOne prop : ones) { - if (prop.getBeanTable() == null) { - if (!prop.isEmbedded()) { - prop.setTransient(); - } - } - } - - List> manys = desc.propertiesAssocMany(); - for (DeployBeanPropertyAssocMany prop : manys) { - if (prop.getBeanTable() == null) { + for (DeployBeanPropertyAssocOne prop : desc.propertiesAssocOne()) { + if (prop.getBeanTable() == null && !prop.isEmbedded()) { prop.setTransient(); } } + for (DeployBeanPropertyAssocMany prop : desc.propertiesAssocMany()) { + if (prop.getBeanTable() == null) { + prop.setTransient(); + } + } } } diff --git a/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java b/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java index 5300979ac..7a04f3bb7 100644 --- a/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java +++ b/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java @@ -327,7 +327,7 @@ public class DLoadContext implements LoadContext { } private BeanProperty getBeanProperty(BeanDescriptor desc, String path) { - return desc.getBeanPropertyFromPath(path); + return desc.findPropertyFromPath(path); } /** diff --git a/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java b/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java index a1fb38a39..d98a62039 100644 --- a/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java +++ b/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java @@ -33,7 +33,7 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex super(parent, property.getBeanDescriptor(), path, defaultBatchSize, queryProps); this.property = property; - this.docStoreMapped = property.getTargetDescriptor().isDocStoreMapped(); + this.docStoreMapped = property.isTargetDocStoreMapped(); // bufferList only required when using query joins (queryFetch) this.bufferList = (!queryFetch) ? null : new ArrayList<>(); this.currentBuffer = createBuffer(firstBatchSize); diff --git a/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java b/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java index 0b3d20e1a..7a9748649 100644 --- a/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java +++ b/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java @@ -29,6 +29,7 @@ import io.ebeaninternal.server.deploy.BeanManager; import io.ebeaninternal.server.deploy.BeanProperty; import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import io.ebeaninternal.server.deploy.BeanPropertySimpleCollection; import io.ebeaninternal.server.deploy.IntersectionRow; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -869,8 +870,7 @@ public final class DefaultPersister implements Persister { SpiTransaction t = request.getTransaction(); // exported ones with cascade save - BeanPropertyAssocOne[] expOnes = desc.propertiesOneExportedSave(); - for (BeanPropertyAssocOne prop : expOnes) { + for (BeanPropertyAssocOne prop : desc.propertiesOneExportedSave()) { // check for partial beans if (request.isLoadedProperty(prop)) { EntityBean detailBean = prop.getValueAsEntityBean(parentBean); @@ -886,12 +886,11 @@ public final class DefaultPersister implements Persister { } // many's with cascade save - BeanPropertyAssocMany[] manys = desc.propertiesManySave(); boolean insertedParent = request.isInsertedParent(); - for (BeanPropertyAssocMany many : manys) { + for (BeanPropertyAssocMany many : desc.propertiesManySave()) { // check that property is loaded and collection should be cascaded to if (request.isLoadedProperty(many) && !many.isSkipSaveBeanCollection(parentBean, insertedParent)) { - saveMany(new SaveManyPropRequest(insertedParent, many, parentBean, request)); + saveMany2(insertedParent, many, parentBean, request); if (!insertedParent) { request.addUpdatedManyProperty(many); } @@ -899,6 +898,18 @@ public final class DefaultPersister implements Persister { } } + private void saveMany2(boolean insertedParent, BeanPropertyAssocMany many, EntityBean parentBean, PersistRequestBean request) { + saveMany(saveManyRequest(insertedParent, many, parentBean, request)); + } + + private SaveManyPropRequest saveManyRequest(boolean insertedParent, BeanPropertyAssocMany many, EntityBean parentBean, PersistRequestBean request) { + if (many instanceof BeanPropertySimpleCollection) { + return new SaveManySimpleCollection(insertedParent, many, parentBean, request); + } else { + return new SaveManyPropRequest(insertedParent, many, parentBean, request); + } + } + private void saveMany(SaveManyPropRequest saveMany) { if (saveMany.getMany().hasJoinTable()) { @@ -1131,8 +1142,7 @@ public final class DefaultPersister implements Persister { } // Many's with delete cascade - BeanPropertyAssocMany[] manys = desc.propertiesManyDelete(); - for (BeanPropertyAssocMany many : manys) { + for (BeanPropertyAssocMany many : desc.propertiesManyDelete()) { if (many.hasJoinTable()) { if (!softDelete) { // delete associated rows from intersection table (but not during soft delete) diff --git a/src/main/java/io/ebeaninternal/server/persist/SaveManyPropRequest.java b/src/main/java/io/ebeaninternal/server/persist/SaveManyPropRequest.java index 97f9d1131..dbd643d2f 100644 --- a/src/main/java/io/ebeaninternal/server/persist/SaveManyPropRequest.java +++ b/src/main/java/io/ebeaninternal/server/persist/SaveManyPropRequest.java @@ -21,11 +21,11 @@ import java.util.Map; */ class SaveManyPropRequest { - private final PersistRequestBean request; + final PersistRequestBean request; private final boolean insertedParent; - private final BeanPropertyAssocMany many; - private final EntityBean parentBean; - private final SpiTransaction transaction; + final BeanPropertyAssocMany many; + final EntityBean parentBean; + final SpiTransaction transaction; private final boolean cascade; private final boolean deleteMissingChildren; private final boolean publish; @@ -35,8 +35,8 @@ class SaveManyPropRequest { private final boolean isMap; private final boolean saveRecurseSkippable; - private Collection collection; - private DefaultPersister persister; + Collection collection; + DefaultPersister persister; private boolean deleteMissing; private int sortOrder; @@ -127,7 +127,7 @@ class SaveManyPropRequest { } } - private void processDetails() { + void processDetails() { BeanProperty orderColumn = null; boolean hasOrderColumn = many.hasOrderColumn(); diff --git a/src/main/java/io/ebeaninternal/server/persist/SaveManySimpleCollection.java b/src/main/java/io/ebeaninternal/server/persist/SaveManySimpleCollection.java new file mode 100644 index 000000000..62faf73e3 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/persist/SaveManySimpleCollection.java @@ -0,0 +1,39 @@ +package io.ebeaninternal.server.persist; + +import io.ebean.SqlUpdate; +import io.ebean.bean.EntityBean; +import io.ebeaninternal.api.SpiEbeanServer; +import io.ebeaninternal.server.core.PersistRequestBean; +import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; + +class SaveManySimpleCollection extends SaveManyPropRequest { + + SaveManySimpleCollection(boolean insertedParent, BeanPropertyAssocMany many, EntityBean parentBean, PersistRequestBean request) { + super(insertedParent, many, parentBean, request); + } + + @Override + void processDetails() { + + Object parentId = request.getBeanId(); + + SqlUpdate sqlDelete = many.deleteByParentId(parentId, null); + + SpiEbeanServer server = request.getServer(); + server.execute(sqlDelete, transaction); + + transaction.depth(+1); + + String insert = many.insertElementCollection(); + SqlUpdate sqlInsert = server.createSqlUpdate(insert); + + for (Object value : collection) { + + sqlInsert.setParameter(1, parentId); + sqlInsert.setParameter(2, value); + server.execute(sqlInsert, transaction); + } + + transaction.depth(-1); + } +} diff --git a/src/main/java/io/ebeaninternal/server/query/CQuery.java b/src/main/java/io/ebeaninternal/server/query/CQuery.java index da7bdd090..9a40afaaf 100644 --- a/src/main/java/io/ebeaninternal/server/query/CQuery.java +++ b/src/main/java/io/ebeaninternal/server/query/CQuery.java @@ -35,7 +35,6 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; @@ -56,6 +55,8 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran private static final Logger logger = LoggerFactory.getLogger(CQuery.class); + private static final CQueryCollectionAddNoop NOOP_ADD = new CQueryCollectionAddNoop(); + /** * The resultSet rows read. */ @@ -99,7 +100,7 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran /** * The help for the 'master' collection. */ - private final BeanCollectionHelp help; + private final CQueryCollectionAdd help; /** * The overall find request wrapper object. @@ -147,7 +148,7 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran /** * For master detail query. */ - private final BeanPropertyAssocMany manyProperty; + private final STreePropertyAssocMany manyProperty; private DataReader dataReader; @@ -180,20 +181,26 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran private long executionTimeMicros; /** - * Flag set when findIterate is being read audited. + * Flag set when read auditing. + */ + private boolean audit; + + /** + * Flag set when findIterate is being read audited meaning we log in batches. */ private boolean auditFindIterate; /** * A buffer of Ids collected for findIterate auditing. */ - private List auditFindIterateIds; + private List auditIds; /** * Create the Sql select based on the request. */ public CQuery(OrmQueryRequest request, CQueryPredicates predicates, CQueryPlan queryPlan) { this.request = request; + this.audit = request.isAuditReads(); this.queryPlan = queryPlan; this.query = request.getQuery(); this.queryMode = query.getMode(); @@ -220,7 +227,11 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran this.logWhereSql = queryPlan.getLogWhereSql(); this.desc = request.getBeanDescriptor(); this.predicates = predicates; - this.help = createHelp(request); + if (lazyLoadManyProperty != null) { + this.help = NOOP_ADD; + } else { + this.help = createHelp(request); + } this.collection = (help != null ? help.createEmptyNoParent() : null); } @@ -233,7 +244,7 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran // subQuery compiled for InQueryExpression return null; } - return BeanCollectionHelpFactory.create(request); + return BeanCollectionHelpFactory.create(manyType, request); } } @@ -374,7 +385,7 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran */ public void close() { try { - if (auditFindIterateIds != null && !auditFindIterateIds.isEmpty()) { + if (auditFindIterate && auditIds != null && !auditIds.isEmpty()) { auditIterateLogMessage(); } } catch (Throwable e) { @@ -510,8 +521,8 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran } protected EntityBean next() { - if (auditFindIterate) { - auditIterateNextBean(); + if (audit) { + auditNextBean(); } hasNextCache = false; if (nextBean == null) { @@ -656,7 +667,7 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran * per SqlSelect. This can be null. */ @Override - public BeanPropertyAssocMany getManyProperty() { + public STreePropertyAssocMany getManyProperty() { return manyProperty; } @@ -747,30 +758,25 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran */ void auditFindMany() { - if (!collection.isEmpty()) { + if (auditIds != null && !auditIds.isEmpty()) { // get the id values of the underlying collection - List ids = new ArrayList<>(collection.size()); - Collection underlyingBeans = collection.getActualDetails(); - for (T underlyingBean : underlyingBeans) { - ids.add(desc.getIdForJson(underlyingBean)); - } ReadEvent futureReadEvent = query.getFutureFetchAudit(); if (futureReadEvent == null) { // normal query execution - desc.readAuditMany(queryPlan.getAuditQueryKey(), bindLog, ids); + desc.readAuditMany(queryPlan.getAuditQueryKey(), bindLog, auditIds); } else { // this query was executed via findFutureList() and the prepare() // has already been called so set the details and log futureReadEvent.setQueryKey(queryPlan.getAuditQueryKey()); futureReadEvent.setBindLog(bindLog); - futureReadEvent.setIds(ids); + futureReadEvent.setIds(auditIds); desc.readAuditFutureMany(futureReadEvent); } } } /** - * Indicate that read auditing is occurring on a this findIterate query. + * Indicate that read auditing is occurring on this findIterate query. */ void auditFindIterate() { auditFindIterate = true; @@ -780,21 +786,21 @@ public class CQuery implements DbReadContext, CancelableQuery, SpiProfileTran * Send the current buffer of findIterate collected ids to the audit log. */ private void auditIterateLogMessage() { - desc.readAuditMany(queryPlan.getAuditQueryKey(), bindLog, auditFindIterateIds); + desc.readAuditMany(queryPlan.getAuditQueryKey(), bindLog, auditIds); // create a new list on demand with the next bean/id - auditFindIterateIds = null; + auditIds = null; } /** * Add the id to the audit id buffer and flush if needed in batches of 100. */ - private void auditIterateNextBean() { + private void auditNextBean() { - if (auditFindIterateIds == null) { - auditFindIterateIds = new ArrayList<>(100); + if (auditIds == null) { + auditIds = new ArrayList<>(100); } - auditFindIterateIds.add(desc.getIdForJson(nextBean)); - if (auditFindIterateIds.size() >= 100) { + auditIds.add(desc.getIdForJson(nextBean)); + if (auditFindIterate && auditIds.size() >= 100) { auditIterateLogMessage(); } } diff --git a/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java b/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java index 1d95f7517..43706cd79 100644 --- a/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java +++ b/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java @@ -18,7 +18,6 @@ import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.server.core.OrmQueryRequest; import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeaninternal.server.deploy.BeanProperty; -import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; import io.ebeaninternal.server.el.ElPropertyValue; import io.ebeaninternal.server.persist.Binder; @@ -524,20 +523,15 @@ class CQueryBuilder { return rawSqlHandler.buildSql(request, predicates, query.getRawSql().getSql()); } - BeanPropertyAssocMany manyProp = select.getManyProperty(); - boolean useSqlLimiter = false; - StringBuilder sb = new StringBuilder(500); - String dbOrderBy = predicates.getDbOrderBy(); if (selectClause != null) { sb.append(selectClause); } else { - - useSqlLimiter = (query.hasMaxRowsOrFirstRow() && manyProp == null); + useSqlLimiter = (query.hasMaxRowsOrFirstRow() && select.getManyProperty() == null); if (!useSqlLimiter) { sb.append("select "); @@ -686,7 +680,7 @@ class CQueryBuilder { throw new IllegalArgumentException("Illegal enum: "+ orderBy); } } - + /** * Append where or and based on the hasWhere flag. */ diff --git a/src/main/java/io/ebeaninternal/server/query/CQueryCollectionAdd.java b/src/main/java/io/ebeaninternal/server/query/CQueryCollectionAdd.java new file mode 100644 index 000000000..85079b309 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/query/CQueryCollectionAdd.java @@ -0,0 +1,21 @@ +package io.ebeaninternal.server.query; + +import io.ebean.bean.BeanCollection; +import io.ebean.bean.EntityBean; + +/** + * Defines interface for adding beans to the collection which might be a List, Set or Map. + */ +public interface CQueryCollectionAdd { + + /** + * Create an empty collection. + */ + BeanCollection createEmptyNoParent(); + + /** + * Add a bean to the List Set or Map. + */ + void add(BeanCollection collection, EntityBean bean, boolean withCheck); + +} diff --git a/src/main/java/io/ebeaninternal/server/query/CQueryCollectionAddNoop.java b/src/main/java/io/ebeaninternal/server/query/CQueryCollectionAddNoop.java new file mode 100644 index 000000000..ac4230999 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/query/CQueryCollectionAddNoop.java @@ -0,0 +1,27 @@ +package io.ebeaninternal.server.query; + +import io.ebean.bean.BeanCollection; +import io.ebean.bean.EntityBean; + +/** + * A NOOP based CQueryCollectionAdd for use with lazy loading many queries where the + * beans loaded into the collection are added to the collection(s) of the parent(s). + */ +class CQueryCollectionAddNoop implements CQueryCollectionAdd { + + /** + * Return null as we are not collecting the beans. + */ + @Override + public BeanCollection createEmptyNoParent() { + return null; + } + + /** + * Do nothing for this case. + */ + @Override + public void add(BeanCollection collection, EntityBean bean, boolean withCheck) { + // do nothing + } +} diff --git a/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java b/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java index a9f7921cd..a0d9a46b1 100644 --- a/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java +++ b/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java @@ -11,7 +11,6 @@ import io.ebeaninternal.metric.MetricFactory; import io.ebeaninternal.metric.TimedMetric; import io.ebeaninternal.server.core.OrmQueryRequest; import io.ebeaninternal.server.core.timezone.DataTimeZone; -import io.ebeaninternal.server.deploy.BeanProperty; import io.ebeaninternal.server.query.CQueryPlanStats.Snapshot; import io.ebeaninternal.server.type.DataBind; import io.ebeaninternal.server.type.DataReader; @@ -74,7 +73,7 @@ public class CQueryPlan { /** * Encrypted properties required additional binding. */ - private final BeanProperty[] encryptedProps; + private final STreeProperty[] encryptedProps; private final CQueryPlanStats stats; @@ -177,9 +176,8 @@ public class CQueryPlan { DataBind bindEncryptedProperties(PreparedStatement stmt, Connection conn) throws SQLException { DataBind dataBind = new DataBind(dataTimeZone, stmt, conn); if (encryptedProps != null) { - for (BeanProperty encryptedProp : encryptedProps) { - String key = encryptedProp.getEncryptKey().getStringValue(); - dataBind.setString(key); + for (STreeProperty encryptedProp : encryptedProps) { + dataBind.setString(encryptedProp.getEncryptKeyAsString()); } } return dataBind; diff --git a/src/main/java/io/ebeaninternal/server/query/CQueryPredicates.java b/src/main/java/io/ebeaninternal/server/query/CQueryPredicates.java index 98fe40941..5df1633c0 100644 --- a/src/main/java/io/ebeaninternal/server/query/CQueryPredicates.java +++ b/src/main/java/io/ebeaninternal/server/query/CQueryPredicates.java @@ -231,7 +231,7 @@ public class CQueryPredicates { } } - BeanPropertyAssocMany manyProperty = request.getManyProperty(); + BeanPropertyAssocMany manyProperty = request.determineMany(); if (manyProperty != null) { OrmQueryProperties chunk = query.getDetail().getChunk(manyProperty.getName(), false); SpiExpressionList filterManyExpr = chunk.getFilterMany(); diff --git a/src/main/java/io/ebeaninternal/server/query/ExtraJoin.java b/src/main/java/io/ebeaninternal/server/query/ExtraJoin.java new file mode 100644 index 000000000..c246c651d --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/query/ExtraJoin.java @@ -0,0 +1,20 @@ +package io.ebeaninternal.server.query; + +public class ExtraJoin { + + private final STreePropertyAssoc property; + private final boolean containsMany; + + public ExtraJoin(STreePropertyAssoc property, boolean containsMany) { + this.property = property; + this.containsMany = containsMany; + } + + public STreePropertyAssoc getProperty() { + return property; + } + + public boolean isContainsMany() { + return containsMany; + } +} diff --git a/src/main/java/io/ebeaninternal/server/query/SqlTreeProperty.java b/src/main/java/io/ebeaninternal/server/query/STreeProperty.java similarity index 85% rename from src/main/java/io/ebeaninternal/server/query/SqlTreeProperty.java rename to src/main/java/io/ebeaninternal/server/query/STreeProperty.java index 2543cc825..340766e9a 100644 --- a/src/main/java/io/ebeaninternal/server/query/SqlTreeProperty.java +++ b/src/main/java/io/ebeaninternal/server/query/STreeProperty.java @@ -8,10 +8,10 @@ import java.util.List; /** * A property in the SQL Tree. - * + *

* A BeanProperty or a dynamically created property based on formula. */ -public interface SqlTreeProperty { +public interface STreeProperty { /** * Return the property name. @@ -38,6 +38,16 @@ public interface SqlTreeProperty { */ boolean isAggregation(); + /** + * Return true if the property is a formula. + */ + boolean isFormula(); + + /** + * Return the encryption key as a string value (when the property is encrypted). + */ + String getEncryptKeyAsString(); + /** * Return the Expression language prefix (join path). */ diff --git a/src/main/java/io/ebeaninternal/server/query/STreePropertyAssoc.java b/src/main/java/io/ebeaninternal/server/query/STreePropertyAssoc.java new file mode 100644 index 000000000..1213d0b55 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/query/STreePropertyAssoc.java @@ -0,0 +1,39 @@ +package io.ebeaninternal.server.query; + +import io.ebean.bean.EntityBean; +import io.ebeaninternal.server.deploy.DbSqlContext; +import io.ebeaninternal.server.deploy.id.IdBinder; + +public interface STreePropertyAssoc extends STreeProperty { + + /** + * Return the extra where clause if set. + */ + String getExtraWhere(); + + /** + * Return the type of the target (other side). + */ + STreeType target(); + + /** + * Return the IdBinder of the underlying type. + */ + IdBinder getIdBinder(); + + /** + * Add a Join with the given alias. + */ + SqlJoinType addJoin(SqlJoinType joinType, String alias2, String alias, DbSqlContext ctx); + + /** + * Add a Join with the given prefix (determining the alias). + */ + SqlJoinType addJoin(SqlJoinType joinType, String prefix, DbSqlContext ctx); + + /** + * Add a bean to the parent. + */ + void setValue(EntityBean parentBean, Object contextBean); + +} diff --git a/src/main/java/io/ebeaninternal/server/query/STreePropertyAssocMany.java b/src/main/java/io/ebeaninternal/server/query/STreePropertyAssocMany.java new file mode 100644 index 000000000..9b6791e2f --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/query/STreePropertyAssocMany.java @@ -0,0 +1,45 @@ +package io.ebeaninternal.server.query; + +import io.ebean.bean.BeanCollection; +import io.ebean.bean.EntityBean; +import io.ebeaninternal.server.deploy.DbSqlContext; +import io.ebeaninternal.server.deploy.TableJoin; + +public interface STreePropertyAssocMany extends STreePropertyAssoc { + + /** + * Append exported columns to the select. + */ + void addSelectExported(DbSqlContext ctx, String prefix); + + /** + * Return true if this is a ManyToMany with history. + */ + boolean isManyToManyWithHistory(); + + /** + * Return a reference collection. + */ + BeanCollection createReferenceIfNull(EntityBean localBean); + + /** + * Return true if the property has a join table. + */ + boolean hasJoinTable(); + + /** + * Return the intersection table join. + */ + TableJoin getIntersectionTableJoin(); + + /** + * Add a bean to the collection. + */ + void addBeanToCollectionWithCreate(EntityBean contextParent, EntityBean detailBean, boolean withCheck); + + /** + * Return true if the property is excluded from history. + */ + boolean isExcludedFromHistory(); + +} diff --git a/src/main/java/io/ebeaninternal/server/query/STreePropertyAssocOne.java b/src/main/java/io/ebeaninternal/server/query/STreePropertyAssocOne.java new file mode 100644 index 000000000..722a43a02 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/query/STreePropertyAssocOne.java @@ -0,0 +1,16 @@ +package io.ebeaninternal.server.query; + +import io.ebeaninternal.server.type.ScalarType; + +public interface STreePropertyAssocOne extends STreePropertyAssoc { + + /** + * Return true if the property is an Id. + */ + boolean isAssocId(); + + /** + * Return the scalar type of the associated id property. + */ + ScalarType getIdScalarType(); +} diff --git a/src/main/java/io/ebeaninternal/server/query/STreeType.java b/src/main/java/io/ebeaninternal/server/query/STreeType.java new file mode 100644 index 000000000..e824272c0 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/query/STreeType.java @@ -0,0 +1,136 @@ +package io.ebeaninternal.server.query; + +import io.ebean.bean.EntityBean; +import io.ebean.bean.PersistenceContext; +import io.ebeaninternal.api.SpiQuery; +import io.ebeaninternal.server.deploy.DbReadContext; +import io.ebeaninternal.server.deploy.InheritInfo; +import io.ebeaninternal.server.deploy.id.IdBinder; + +/** + * Bean type interface for Sql query tree. + */ +public interface STreeType { + + /** + * Return the bean short name. + */ + String getName(); + + /** + * Return true if the underlying type has an Id property. + */ + boolean hasId(); + + /** + * Return true if the type is for ElementCollection (not mapped to an entity type/class). + */ + boolean isElementType(); + + /** + * Return true if the type uses soft delete. + */ + boolean isSoftDelete(); + + /** + * Return true if the type uses history. + */ + boolean isHistorySupport(); + + /** + * Return true if the type is RawSql based. + */ + boolean isRawSqlBased(); + + /** + * Return the soft delete predicate using the given table alias. + */ + String getSoftDeletePredicate(String baseTableAlias); + + /** + * Return the scalar properties. + */ + STreeProperty[] propsBaseScalar(); + + /** + * Return the embedded bean properties. + */ + STreePropertyAssoc[] propsEmbedded(); + + /** + * Return the associated one properties. + */ + STreePropertyAssocOne[] propsOne(); + + /** + * Return the associated many properties. + */ + STreePropertyAssocMany[] propsMany(); + + /** + * Return the inheritance information for this type. + */ + InheritInfo getInheritInfo(); + + /** + * Return the IdBinder for this type. + */ + IdBinder getIdBinder(); + + /** + * Create a new entity bean instance. + */ + EntityBean createEntityBean(); + + /** + * Put the entity bean into the persistence context. + */ + Object contextPutIfAbsent(PersistenceContext persistenceContext, Object id, EntityBean localBean); + + /** + * Set draft status on the entity bean. + */ + void setDraft(EntityBean localBean); + + /** + * Invoke any post load listeners. + */ + void postLoad(Object localBean); + + /** + * Return the base table to use given the temporalMode. + */ + String getBaseTable(SpiQuery.TemporalMode temporalMode); + + /** + * Return true if the given path is an embedded bean. + */ + boolean isEmbeddedPath(String propertyPath); + + /** + * Return the bean property traversing the object graph and taking into account inheritance. + */ + STreeProperty findPropertyFromPath(String property); + + /** + * Find a known property. + */ + STreeProperty findProperty(String propName); + + /** + * Find and return property allowing for dynamic formula properties. + */ + STreeProperty findPropertyWithDynamic(String baseName); + + /** + * Return an extra join if the property path requires it. + */ + ExtraJoin extraJoin(String propertyPath); + + /** + * Load the property taking into account inheritance. + */ + void inheritanceLoad(SqlBeanLoad sqlBeanLoad, STreeProperty property, DbReadContext ctx); + + +} diff --git a/src/main/java/io/ebeaninternal/server/query/SqlTree.java b/src/main/java/io/ebeaninternal/server/query/SqlTree.java index 8e7d1a2ee..d4207a9e8 100644 --- a/src/main/java/io/ebeaninternal/server/query/SqlTree.java +++ b/src/main/java/io/ebeaninternal/server/query/SqlTree.java @@ -1,8 +1,6 @@ package io.ebeaninternal.server.query; import io.ebeaninternal.api.SpiQuery; -import io.ebeaninternal.server.deploy.BeanProperty; -import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; import java.util.ArrayList; import java.util.List; @@ -18,7 +16,7 @@ class SqlTree { /** * Property if resultSet contains master and detail rows. */ - private final BeanPropertyAssocMany manyProperty; + private final STreePropertyAssocMany manyProperty; private final Set includes; @@ -38,7 +36,7 @@ class SqlTree { /** * Encrypted Properties require additional binding. */ - private final BeanProperty[] encryptedProps; + private final STreeProperty[] encryptedProps; /** * Where clause for inheritance. @@ -51,7 +49,7 @@ class SqlTree { * Create the SqlSelectClause. */ SqlTree(String summary, SqlTreeNode rootNode, String distinctOn, String selectSql, String fromSql, String groupBy, String inheritanceWhereSql, - BeanProperty[] encryptedProps, BeanPropertyAssocMany manyProperty, Set includes, boolean includeJoins) { + STreeProperty[] encryptedProps, STreePropertyAssocMany manyProperty, Set includes, boolean includeJoins) { this.summary = summary; this.rootNode = rootNode; @@ -147,11 +145,11 @@ class SqlTree { * Return the property that is associated with the many. There can only be one * per SqlSelect. This can be null. */ - BeanPropertyAssocMany getManyProperty() { + STreePropertyAssocMany getManyProperty() { return manyProperty; } - BeanProperty[] getEncryptedProps() { + STreeProperty[] getEncryptedProps() { return encryptedProps; } diff --git a/src/main/java/io/ebeaninternal/server/query/SqlTreeAlias.java b/src/main/java/io/ebeaninternal/server/query/SqlTreeAlias.java index 5e8319a1e..ea583c833 100644 --- a/src/main/java/io/ebeaninternal/server/query/SqlTreeAlias.java +++ b/src/main/java/io/ebeaninternal/server/query/SqlTreeAlias.java @@ -1,17 +1,14 @@ package io.ebeaninternal.server.query; import io.ebean.util.SplitName; -import io.ebeaninternal.server.deploy.BeanDescriptor; -import io.ebeaninternal.server.el.ElPropertyDeploy; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.persistence.PersistenceException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Special Map of the logical property joins to table alias. @@ -61,16 +58,11 @@ class SqlTreeAlias { /** * Add joins. */ - public void addJoin(Set propJoins, BeanDescriptor desc) { + public void addJoin(Set propJoins, STreeType desc) { if (propJoins != null) { for (String propJoin : propJoins) { - ElPropertyDeploy elProp = desc.getElPropertyDeploy(propJoin); - if (elProp == null) { - throw new PersistenceException("Invalid path " + propJoin + " from " + desc.getFullName()); - - } else if (elProp.getBeanProperty().isEmbedded()) { + if (desc.isEmbeddedPath(propJoin)) { addEmbeddedPropertyJoin(propJoin); - } else { addPropertyJoin(propJoin, joinProps); } diff --git a/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java b/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java index 8811096eb..9cfaad7e8 100644 --- a/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java +++ b/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java @@ -6,14 +6,8 @@ import io.ebeaninternal.api.PropertyJoin; import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.api.SpiQuery.Type; import io.ebeaninternal.server.core.OrmQueryRequest; -import io.ebeaninternal.server.deploy.BeanDescriptor; -import io.ebeaninternal.server.deploy.BeanProperty; -import io.ebeaninternal.server.deploy.BeanPropertyAssoc; -import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; -import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; import io.ebeaninternal.server.deploy.InheritInfo; import io.ebeaninternal.server.deploy.TableJoin; -import io.ebeaninternal.server.el.ElPropertyValue; import io.ebeaninternal.server.querydefn.OrmQueryDetail; import io.ebeaninternal.server.querydefn.OrmQueryProperties; import org.slf4j.Logger; @@ -37,7 +31,7 @@ public final class SqlTreeBuilder { private final SpiQuery query; - private final BeanDescriptor desc; + private final STreeType desc; private final OrmQueryDetail queryDetail; @@ -51,7 +45,7 @@ public final class SqlTreeBuilder { /** * Property if resultSet contains master and detail rows. */ - private BeanPropertyAssocMany manyProperty; + private STreePropertyAssocMany manyProperty; private final SqlTreeAlias alias; @@ -141,7 +135,7 @@ public final class SqlTreeBuilder { String fromSql = null; String inheritanceWhereSql = null; String groupBy = null; - BeanProperty[] encryptedProps = null; + STreeProperty[] encryptedProps = null; if (!rawSql) { selectSql = buildSelectClause(); fromSql = buildFromClause(); @@ -233,7 +227,7 @@ public final class SqlTreeBuilder { return ctx.getContent(); } - private void buildRoot(BeanDescriptor desc) { + private void buildRoot(STreeType desc) { rootNode = buildSelectChain(null, null, desc, null); @@ -253,26 +247,24 @@ public final class SqlTreeBuilder { * Recursively build the query tree depending on what leaves in the tree * should be included. */ - private SqlTreeNode buildSelectChain(String prefix, BeanPropertyAssoc prop, - BeanDescriptor desc, List joinList) { + private SqlTreeNode buildSelectChain(String prefix, STreePropertyAssoc prop, + STreeType desc, List joinList) { List myJoinList = new ArrayList<>(); - BeanPropertyAssocOne[] ones = desc.propertiesOne(); - for (BeanPropertyAssocOne one : ones) { + for (STreePropertyAssocOne one : desc.propsOne()) { String propPrefix = SplitName.add(prefix, one.getName()); if (isIncludeBean(propPrefix)) { selectIncludes.add(propPrefix); - buildSelectChain(propPrefix, one, one.getTargetDescriptor(), myJoinList); + buildSelectChain(propPrefix, one, one.target(), myJoinList); } } - BeanPropertyAssocMany[] manys = desc.propertiesMany(); - for (BeanPropertyAssocMany many : manys) { + for (STreePropertyAssocMany many : desc.propsMany()) { String propPrefix = SplitName.add(prefix, many.getName()); if (isIncludeMany(propPrefix, many)) { selectIncludes.add(propPrefix); - buildSelectChain(propPrefix, many, many.getTargetDescriptor(), myJoinList); + buildSelectChain(propPrefix, many, many.target(), myJoinList); } } @@ -304,24 +296,24 @@ public final class SqlTreeBuilder { Collection includes = manyWhereJoins.getPropertyJoins(); for (PropertyJoin joinProp : includes) { - BeanPropertyAssoc beanProperty = (BeanPropertyAssoc) desc.getBeanPropertyFromPath(joinProp.getProperty()); + STreePropertyAssoc beanProperty = (STreePropertyAssoc) desc.findPropertyFromPath(joinProp.getProperty()); SqlTreeNodeManyWhereJoin nodeJoin = new SqlTreeNodeManyWhereJoin(joinProp.getProperty(), beanProperty, joinProp.getSqlJoinType()); myJoinList.add(nodeJoin); } } - private SqlTreeNode buildNode(String prefix, BeanPropertyAssoc prop, BeanDescriptor desc, List myList, SqlTreeProperties props) { + private SqlTreeNode buildNode(String prefix, STreePropertyAssoc prop, STreeType desc, List myList, SqlTreeProperties props) { if (prefix == null) { buildExtraJoins(desc, myList); // Optional many property for lazy loading query - BeanPropertyAssocMany lazyLoadMany = (query == null) ? null : query.getLazyLoadMany(); + STreePropertyAssocMany lazyLoadMany = (query == null) ? null : query.getLazyLoadMany(); boolean withId = !rawNoId && !subQuery && (query == null || query.isWithId()); return new SqlTreeNodeRoot(desc, props, myList, withId, includeJoin, lazyLoadMany, temporalMode, disableLazyLoad); - } else if (prop instanceof BeanPropertyAssocMany) { - return new SqlTreeNodeManyRoot(prefix, (BeanPropertyAssocMany) prop, props, myList, temporalMode, disableLazyLoad); + } else if (prop instanceof STreePropertyAssocMany) { + return new SqlTreeNodeManyRoot(prefix, (STreePropertyAssocMany) prop, props, myList, temporalMode, disableLazyLoad); } else { // do not read Id on child beans (e.g. when used with fetch()) @@ -334,7 +326,7 @@ public final class SqlTreeBuilder { * Build extra joins to support properties used in where clause but not * already in select clause. */ - private void buildExtraJoins(BeanDescriptor desc, List myList) { + private void buildExtraJoins(STreeType desc, List myList) { if (rawSql) { return; @@ -383,25 +375,25 @@ public final class SqlTreeBuilder { * This means it can included individual properties of an embedded bean. *

*/ - private void addPropertyToSubQuery(SqlTreeProperties selectProps, BeanDescriptor desc, String propName) { + private void addPropertyToSubQuery(SqlTreeProperties selectProps, STreeType desc, String propName) { - BeanProperty p = desc.findBeanProperty(propName); + STreeProperty p = desc.findProperty(propName); if (p == null) { logger.error("property [" + propName + "]not found on " + desc + " for query - excluding it."); - } else if (p instanceof BeanPropertyAssoc && p.isEmbedded()) { + } else if (p instanceof STreePropertyAssoc && p.isEmbedded()) { // if the property is embedded we need to lookup the real column name int pos = propName.indexOf('.'); if (pos > -1) { String name = propName.substring(pos + 1); - p = ((BeanPropertyAssoc) p).getTargetDescriptor().findBeanProperty(name); + p = ((STreePropertyAssoc) p).target().findProperty(name); } } selectProps.add(p); } - private void addProperty(SqlTreeProperties selectProps, BeanDescriptor desc, + private void addProperty(SqlTreeProperties selectProps, STreeType desc, OrmQueryProperties queryProps, String propName) { if (subQuery) { @@ -418,7 +410,7 @@ public final class SqlTreeBuilder { // make sure we only included the base/embedded bean once if (!selectProps.containsProperty(baseName)) { - SqlTreeProperty p = desc.findSqlTreeProperty(baseName); + STreeProperty p = desc.findPropertyWithDynamic(baseName); if (p == null) { logger.error("property [" + propName + "] not found on " + desc + " for query - excluding it."); @@ -436,17 +428,17 @@ public final class SqlTreeBuilder { } else { // find the property including searching the // sub class hierarchy if required - SqlTreeProperty p = desc.findSqlTreeProperty(propName); + STreeProperty p = desc.findPropertyWithDynamic(propName); if (p == null) { logger.error("property [" + propName + "] not found on " + desc + " for query - excluding it."); - p = desc.findBeanProperty("id"); + p = desc.findProperty("id"); selectProps.add(p); } else if (p.isId() && excludeIdProperty()) { // do not bother to include id for normal queries as the // id is always added (except for subQueries) - } else if (p instanceof BeanPropertyAssoc) { + } else if (p instanceof STreePropertyAssoc) { // need to check if this property should be // excluded. This occurs when this property is // included as a bean join. With a bean join @@ -463,7 +455,7 @@ public final class SqlTreeBuilder { } } - private SqlTreeProperties getBaseSelectPartial(BeanDescriptor desc, OrmQueryProperties queryProps) { + private SqlTreeProperties getBaseSelectPartial(STreeType desc, OrmQueryProperties queryProps) { SqlTreeProperties selectProps = new SqlTreeProperties(); selectProps.setReadOnly(queryProps.isReadOnly()); @@ -484,7 +476,7 @@ public final class SqlTreeBuilder { return selectProps; } - private SqlTreeProperties getBaseSelect(BeanDescriptor desc, OrmQueryProperties queryProps) { + private SqlTreeProperties getBaseSelect(STreeType desc, OrmQueryProperties queryProps) { boolean partial = queryProps != null && !queryProps.allProperties(); if (partial) { @@ -495,17 +487,16 @@ public final class SqlTreeBuilder { selectProps.setAllProperties(); // normal simple properties of the bean - selectProps.add(desc.propertiesBaseScalar()); - selectProps.add(desc.propertiesEmbedded()); + selectProps.add(desc.propsBaseScalar()); + selectProps.add(desc.propsEmbedded()); - BeanPropertyAssocOne[] propertiesOne = desc.propertiesOne(); - for (BeanPropertyAssocOne aPropertiesOne : propertiesOne) { + for (STreePropertyAssocOne propertyAssocOne : desc.propsOne()) { //noinspection StatementWithEmptyBody - if (queryProps != null && queryProps.isIncludedBeanJoin(aPropertiesOne.getName())) { + if (queryProps != null && queryProps.isIncludedBeanJoin(propertyAssocOne.getName())) { // if it is a joined bean... then don't add the property // as it will have its own entire Node in the SqlTree } else { - selectProps.add(aPropertiesOne); + selectProps.add(propertyAssocOne); } } @@ -521,7 +512,7 @@ public final class SqlTreeBuilder { /** * Return true if this many node should be included in the query. */ - private boolean isIncludeMany(String propName, BeanPropertyAssocMany manyProp) { + private boolean isIncludeMany(String propName, STreePropertyAssocMany manyProp) { if (queryDetail.isJoinsEmpty()) { return false; @@ -588,9 +579,9 @@ public final class SqlTreeBuilder { */ private final Map rootRegister = new HashMap<>(); - private final BeanDescriptor desc; + private final STreeType desc; - private IncludesDistiller(BeanDescriptor desc, Set selectIncludes, + private IncludesDistiller(STreeType desc, Set selectIncludes, Set predicateIncludes) { this.desc = desc; this.selectIncludes = selectIncludes; @@ -645,25 +636,14 @@ public final class SqlTreeBuilder { */ private SqlTreeNodeExtraJoin createJoinLeaf(String propertyName) { - ElPropertyValue elGetValue = desc.getElGetValue(propertyName); - - if (elGetValue == null) { - // this can occur for master detail queries - // with concatenated keys (so not an error now) + ExtraJoin extra = desc.extraJoin(propertyName); + if (extra == null) { return null; - } - BeanProperty beanProperty = elGetValue.getBeanProperty(); - if (beanProperty instanceof BeanPropertyAssoc) { - BeanPropertyAssoc assocProp = (BeanPropertyAssoc) beanProperty; - if (assocProp.isEmbedded()) { - // no extra join required for embedded beans - return null; - } - SqlTreeNodeExtraJoin extraJoin = new SqlTreeNodeExtraJoin(propertyName, assocProp, elGetValue.containsMany()); + } else { + SqlTreeNodeExtraJoin extraJoin = new SqlTreeNodeExtraJoin(propertyName, extra.getProperty(), extra.isContainsMany()); joinRegister.put(propertyName, extraJoin); return extraJoin; } - return null; } /** @@ -674,10 +654,8 @@ public final class SqlTreeBuilder { * not specified and is implicitly created. *

*/ - private SqlTreeNodeExtraJoin findExtraJoinRoot(String includeProp, - SqlTreeNodeExtraJoin childJoin) { + private SqlTreeNodeExtraJoin findExtraJoinRoot(String includeProp, SqlTreeNodeExtraJoin childJoin) { while (true) { - int dotPos = includeProp.lastIndexOf('.'); if (dotPos == -1) { // no parent possible(parent is root) diff --git a/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeBean.java b/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeBean.java index 806e64adb..96fd09d05 100644 --- a/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeBean.java +++ b/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeBean.java @@ -9,11 +9,6 @@ import io.ebean.util.SplitName; import io.ebean.util.StringHelper; import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.api.SpiQuery.Mode; -import io.ebeaninternal.server.deploy.BeanDescriptor; -import io.ebeaninternal.server.deploy.BeanProperty; -import io.ebeaninternal.server.deploy.BeanPropertyAssoc; -import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; -import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; import io.ebeaninternal.server.deploy.DbReadContext; import io.ebeaninternal.server.deploy.DbSqlContext; import io.ebeaninternal.server.deploy.InheritInfo; @@ -34,7 +29,7 @@ class SqlTreeNodeBean implements SqlTreeNode { private static final SqlTreeNode[] NO_CHILDREN = new SqlTreeNode[0]; - protected final BeanDescriptor desc; + protected final STreeType desc; protected final IdBinder idBinder; @@ -48,14 +43,14 @@ class SqlTreeNodeBean implements SqlTreeNode { */ private final boolean partialObject; - protected final SqlTreeProperty[] properties; + protected final STreeProperty[] properties; /** * Extra where clause added by Where annotation on associated many. */ private final String extraWhere; - private final BeanPropertyAssoc nodeBeanProp; + private final STreePropertyAssoc nodeBeanProp; /** * False if report bean and has no id property. @@ -70,7 +65,7 @@ class SqlTreeNodeBean implements SqlTreeNode { private final Map pathMap; - final BeanPropertyAssocMany lazyLoadParent; + final STreePropertyAssocMany lazyLoadParent; final SpiQuery.TemporalMode temporalMode; @@ -92,29 +87,29 @@ class SqlTreeNodeBean implements SqlTreeNode { /** * Construct for leaf node. */ - SqlTreeNodeBean(String prefix, BeanPropertyAssoc beanProp, SqlTreeProperties props, + SqlTreeNodeBean(String prefix, STreePropertyAssoc beanProp, SqlTreeProperties props, List myChildren, boolean withId, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) { - this(prefix, beanProp, beanProp.getTargetDescriptor(), props, myChildren, withId, null, temporalMode, disableLazyLoad); + this(prefix, beanProp, beanProp.target(), props, myChildren, withId, null, temporalMode, disableLazyLoad); } /** * Construct for root node. */ - SqlTreeNodeBean(BeanDescriptor desc, SqlTreeProperties props, List myList, boolean withId, - BeanPropertyAssocMany many, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) { + SqlTreeNodeBean(STreeType desc, SqlTreeProperties props, List myList, boolean withId, + STreePropertyAssocMany many, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) { this(null, null, desc, props, myList, withId, many, temporalMode, disableLazyLoad); } /** * Create with the appropriate node. */ - private SqlTreeNodeBean(String prefix, BeanPropertyAssoc beanProp, BeanDescriptor desc, SqlTreeProperties props, - List myChildren, boolean withId, BeanPropertyAssocMany lazyLoadParent, + private SqlTreeNodeBean(String prefix, STreePropertyAssoc beanProp, STreeType desc, SqlTreeProperties props, + List myChildren, boolean withId, STreePropertyAssocMany lazyLoadParent, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) { this.lazyLoadParent = lazyLoadParent; - this.lazyLoadParentIdBinder = (lazyLoadParent == null) ? null : lazyLoadParent.getBeanDescriptor().getIdBinder(); + this.lazyLoadParentIdBinder = (lazyLoadParent == null) ? null : lazyLoadParent.getIdBinder(); this.prefix = prefix; this.desc = desc; this.inheritInfo = desc.getInheritInfo(); @@ -129,7 +124,7 @@ class SqlTreeNodeBean implements SqlTreeNode { this.aggregationRoot = props.isAggregationRoot(); // the bean has an Id property and we want to use it - this.readId = !aggregationRoot && withId && (desc.getIdProperty() != null); + this.readId = !aggregationRoot && withId && desc.hasId(); this.disableLazyLoad = disableLazyLoad || !readId || desc.isRawSqlBased() || temporalVersions; this.partialObject = props.isPartialObject(); @@ -150,25 +145,22 @@ class SqlTreeNodeBean implements SqlTreeNode { // if we have also no children, NPE happens anyway. return children[0].getSingleAttributeScalarType(); } - if (properties[0] instanceof BeanPropertyAssocOne) { - BeanPropertyAssocOne assocOne = (BeanPropertyAssocOne)properties[0]; + if (properties[0] instanceof STreePropertyAssocOne) { + STreePropertyAssocOne assocOne = (STreePropertyAssocOne)properties[0]; if (assocOne.isAssocId()) { - return assocOne.getTargetDescriptor().getIdProperty().getScalarType(); + return assocOne.getIdScalarType(); } } return properties[0].getScalarType(); } - private Map createPathMap(String prefix, BeanDescriptor desc) { - - BeanPropertyAssocMany[] manys = desc.propertiesMany(); + private Map createPathMap(String prefix, STreeType desc) { HashMap m = new HashMap<>(); - for (BeanPropertyAssocMany many : manys) { + for (STreePropertyAssocMany many : desc.propsMany()) { String name = many.getName(); m.put(name, getPath(prefix, name)); } - return m; } @@ -189,7 +181,7 @@ class SqlTreeNodeBean implements SqlTreeNode { } idBinder.buildRawSqlSelectChain(prefix, selectChain); } - for (SqlTreeProperty property : properties) { + for (STreeProperty property : properties) { property.buildRawSqlSelectChain(prefix, selectChain); } // recursively continue reading... @@ -228,7 +220,7 @@ class SqlTreeNodeBean implements SqlTreeNode { } Class localType; - BeanDescriptor localDesc; + STreeType localDesc; IdBinder localIdBinder; EntityBean localBean; @@ -292,21 +284,16 @@ class SqlTreeNodeBean implements SqlTreeNode { if (inheritInfo == null) { // normal behavior with no inheritance - for (SqlTreeProperty property : properties) { + for (STreeProperty property : properties) { property.load(sqlBeanLoad); } } else { // take account of inheritance and due to subclassing approach // need to get a 'local' version of the property - for (SqlTreeProperty property : properties) { + for (STreeProperty property : properties) { // get a local version of the BeanProperty - BeanProperty p = localDesc.getBeanProperty(property.getName()); - if (p != null) { - p.load(sqlBeanLoad); - } else { - property.loadIgnore(ctx); - } + localDesc.inheritanceLoad(sqlBeanLoad, property, ctx); } } @@ -374,6 +361,9 @@ class SqlTreeNodeBean implements SqlTreeNode { if (!readId || temporalVersions) { // a bean with no Id (never found in context) + if (lazyLoadParentId != null && desc.isElementType()) { + ctx.setLazyLoadedChildBean(localBean, lazyLoadParentId); + } return localBean; } else { @@ -388,13 +378,12 @@ class SqlTreeNodeBean implements SqlTreeNode { * Create lazy loading proxies for the Many's except for the one that is * included in the actual query. */ - private void createListProxies(BeanDescriptor localDesc, DbReadContext ctx, EntityBean localBean, boolean disableLazyLoad) { + private void createListProxies(STreeType localDesc, DbReadContext ctx, EntityBean localBean, boolean disableLazyLoad) { - BeanPropertyAssocMany fetchedMany = ctx.getManyProperty(); + STreePropertyAssocMany fetchedMany = ctx.getManyProperty(); // load the List/Set/Map proxy objects (deferred fetching of lists) - BeanPropertyAssocMany[] manys = localDesc.propertiesMany(); - for (BeanPropertyAssocMany many : manys) { + for (STreePropertyAssocMany many : localDesc.propsMany()) { if (fetchedMany == null || !fetchedMany.equals(many)) { // create a proxy for the many (deferred fetching) @@ -419,7 +408,7 @@ class SqlTreeNodeBean implements SqlTreeNode { if (readId) { appendSelectId(ctx, idBinder.getBeanProperty()); } - for (SqlTreeProperty property : properties) { + for (STreeProperty property : properties) { if (!property.isAggregation()) { property.appendSelect(ctx, subQuery); } @@ -486,14 +475,13 @@ class SqlTreeNodeBean implements SqlTreeNode { /** * Append the properties to the buffer. */ - private void appendSelect(DbSqlContext ctx, boolean subQuery, SqlTreeProperty[] props) { - - for (SqlTreeProperty prop : props) { + private void appendSelect(DbSqlContext ctx, boolean subQuery, STreeProperty[] props) { + for (STreeProperty prop : props) { prop.appendSelect(ctx, subQuery); } } - protected void appendSelectId(DbSqlContext ctx, BeanProperty prop) { + protected void appendSelectId(DbSqlContext ctx, STreeProperty prop) { if (prop != null) { prop.appendSelect(ctx, false); } @@ -546,7 +534,7 @@ class SqlTreeNodeBean implements SqlTreeNode { // join and return SqlJoinType to use for child joins joinType = appendFromBaseTable(ctx, joinType); - for (SqlTreeProperty property : properties) { + for (STreeProperty property : properties) { // usually nothing... except for 1-1 Exported property.appendFrom(ctx, joinType); } @@ -601,8 +589,8 @@ class SqlTreeNodeBean implements SqlTreeNode { private SqlJoinType appendFromAsJoin(DbSqlContext ctx, SqlJoinType joinType) { - if (nodeBeanProp instanceof BeanPropertyAssocMany) { - BeanPropertyAssocMany manyProp = (BeanPropertyAssocMany) nodeBeanProp; + if (nodeBeanProp instanceof STreePropertyAssocMany) { + STreePropertyAssocMany manyProp = (STreePropertyAssocMany) nodeBeanProp; if (manyProp.hasJoinTable()) { String alias = ctx.getTableAlias(prefix); @@ -619,7 +607,6 @@ class SqlTreeNodeBean implements SqlTreeNode { return nodeBeanProp.addJoin(joinType, alias2, alias, ctx); } - } return nodeBeanProp.addJoin(joinType, prefix, ctx); diff --git a/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java b/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java index ba83d4ced..17943ddd1 100644 --- a/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java +++ b/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java @@ -4,14 +4,11 @@ import io.ebean.Version; import io.ebean.bean.EntityBean; import io.ebean.util.SplitName; import io.ebeaninternal.api.SpiQuery; -import io.ebeaninternal.server.deploy.BeanPropertyAssoc; -import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; import io.ebeaninternal.server.deploy.DbReadContext; import io.ebeaninternal.server.deploy.DbSqlContext; import io.ebeaninternal.server.deploy.TableJoin; import io.ebeaninternal.server.type.ScalarType; -import java.sql.SQLException; import java.util.ArrayList; import java.util.List; @@ -25,7 +22,7 @@ import java.util.List; */ class SqlTreeNodeExtraJoin implements SqlTreeNode { - private final BeanPropertyAssoc assocBeanProperty; + private final STreePropertyAssoc assocBeanProperty; private final String prefix; @@ -35,11 +32,11 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode { private List children; - SqlTreeNodeExtraJoin(String prefix, BeanPropertyAssoc assocBeanProperty, boolean pathContainsMany) { + SqlTreeNodeExtraJoin(String prefix, STreePropertyAssoc assocBeanProperty, boolean pathContainsMany) { this.prefix = prefix; this.assocBeanProperty = assocBeanProperty; this.pathContainsMany = pathContainsMany; - this.manyJoin = assocBeanProperty instanceof BeanPropertyAssocMany; + this.manyJoin = assocBeanProperty instanceof STreePropertyAssocMany; } @Override @@ -103,8 +100,8 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode { boolean manyToMany = false; - if (assocBeanProperty instanceof BeanPropertyAssocMany) { - BeanPropertyAssocMany manyProp = (BeanPropertyAssocMany) assocBeanProperty; + if (assocBeanProperty instanceof STreePropertyAssocMany) { + STreePropertyAssocMany manyProp = (STreePropertyAssocMany) assocBeanProperty; if (manyProp.hasJoinTable()) { manyToMany = true; @@ -127,7 +124,7 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode { } if (!manyToMany) { if (assocBeanProperty.isFormula()) { - // add joins for formula beans + // add joins for formula beans assocBeanProperty.appendFrom(ctx, joinType); } joinType = assocBeanProperty.addJoin(joinType, prefix, ctx); @@ -164,7 +161,7 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode { * Does nothing. */ @Override - public EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean parentBean) throws SQLException { + public EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean parentBean) { return null; } @@ -172,7 +169,7 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode { * Does nothing. */ @Override - public Version loadVersion(DbReadContext ctx) throws SQLException { + public Version loadVersion(DbReadContext ctx) { return null; } diff --git a/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java b/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java index 835d201b3..edeff9fce 100644 --- a/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java +++ b/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java @@ -2,7 +2,6 @@ package io.ebeaninternal.server.query; import io.ebean.bean.EntityBean; import io.ebeaninternal.api.SpiQuery; -import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; import io.ebeaninternal.server.deploy.DbReadContext; import io.ebeaninternal.server.deploy.DbSqlContext; @@ -11,9 +10,9 @@ import java.util.List; final class SqlTreeNodeManyRoot extends SqlTreeNodeBean { - private final BeanPropertyAssocMany manyProp; + private final STreePropertyAssocMany manyProp; - SqlTreeNodeManyRoot(String prefix, BeanPropertyAssocMany prop, SqlTreeProperties props, List myList, + SqlTreeNodeManyRoot(String prefix, STreePropertyAssocMany prop, SqlTreeProperties props, List myList, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) { super(prefix, prop, props, myList, true, temporalMode, disableLazyLoad); this.manyProp = prop; diff --git a/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java b/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java index 07ab86398..ae0a810ab 100644 --- a/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java +++ b/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java @@ -4,15 +4,11 @@ import io.ebean.Version; import io.ebean.bean.EntityBean; import io.ebean.util.SplitName; import io.ebeaninternal.api.SpiQuery; -import io.ebeaninternal.server.deploy.BeanPropertyAssoc; -import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; -import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; import io.ebeaninternal.server.deploy.DbReadContext; import io.ebeaninternal.server.deploy.DbSqlContext; import io.ebeaninternal.server.deploy.TableJoin; import io.ebeaninternal.server.type.ScalarType; -import java.sql.SQLException; import java.util.List; /** @@ -24,15 +20,14 @@ class SqlTreeNodeManyWhereJoin implements SqlTreeNode { private final String prefix; - private final BeanPropertyAssoc nodeBeanProp; + private final STreePropertyAssoc nodeBeanProp; /** * The many where join which is either INNER or OUTER. */ private final SqlJoinType manyJoinType; - SqlTreeNodeManyWhereJoin(String prefix, BeanPropertyAssoc prop, SqlJoinType manyJoinType) { - + SqlTreeNodeManyWhereJoin(String prefix, STreePropertyAssoc prop, SqlJoinType manyJoinType) { this.nodeBeanProp = prop; this.prefix = prefix; this.manyJoinType = manyJoinType; @@ -86,16 +81,16 @@ class SqlTreeNodeManyWhereJoin implements SqlTreeNode { * Join to base table for this node. This includes a join to the * intersection table if this is a ManyToMany node. */ - void appendFromBaseTable(DbSqlContext ctx, SqlJoinType joinType) { + private void appendFromBaseTable(DbSqlContext ctx, SqlJoinType joinType) { String alias = ctx.getTableAliasManyWhere(prefix); String parentAlias = ctx.getTableAliasManyWhere(parentPrefix); - if (nodeBeanProp instanceof BeanPropertyAssocOne) { + if (nodeBeanProp instanceof STreePropertyAssocOne) { nodeBeanProp.addJoin(joinType, parentAlias, alias, ctx); } else { - BeanPropertyAssocMany manyProp = (BeanPropertyAssocMany) nodeBeanProp; + STreePropertyAssocMany manyProp = (STreePropertyAssocMany) nodeBeanProp; if (!manyProp.hasJoinTable()) { manyProp.addJoin(joinType, parentAlias, alias, ctx); @@ -125,13 +120,13 @@ class SqlTreeNodeManyWhereJoin implements SqlTreeNode { } @Override - public EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean parentBean) throws SQLException { + public EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean parentBean) { // nothing to do here return null; } @Override - public Version loadVersion(DbReadContext ctx) throws SQLException { + public Version loadVersion(DbReadContext ctx) { // nothing to do here return null; } diff --git a/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeRoot.java b/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeRoot.java index bb93b25e1..74be7c415 100644 --- a/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeRoot.java +++ b/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeRoot.java @@ -1,8 +1,6 @@ package io.ebeaninternal.server.query; import io.ebeaninternal.api.SpiQuery; -import io.ebeaninternal.server.deploy.BeanDescriptor; -import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; import io.ebeaninternal.server.deploy.DbSqlContext; import io.ebeaninternal.server.deploy.TableJoin; @@ -18,8 +16,8 @@ final class SqlTreeNodeRoot extends SqlTreeNodeBean { /** * Specify for SqlSelect to include an Id property or not. */ - SqlTreeNodeRoot(BeanDescriptor desc, SqlTreeProperties props, List myList, boolean withId, - TableJoin includeJoin, BeanPropertyAssocMany many, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) { + SqlTreeNodeRoot(STreeType desc, SqlTreeProperties props, List myList, boolean withId, + TableJoin includeJoin, STreePropertyAssocMany many, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) { super(desc, props, myList, withId, many, temporalMode, disableLazyLoad); this.includeJoin = includeJoin; diff --git a/src/main/java/io/ebeaninternal/server/query/SqlTreeProperties.java b/src/main/java/io/ebeaninternal/server/query/SqlTreeProperties.java index 9c717173e..5ec9b286d 100644 --- a/src/main/java/io/ebeaninternal/server/query/SqlTreeProperties.java +++ b/src/main/java/io/ebeaninternal/server/query/SqlTreeProperties.java @@ -20,7 +20,7 @@ public class SqlTreeProperties { /** * The bean properties in order. */ - private final List propsList = new ArrayList<>(); + private final List propsList = new ArrayList<>(); /** * Maintain a list of property names to detect embedded bean additions. @@ -40,17 +40,17 @@ public class SqlTreeProperties { return propNames.contains(propName); } - public void add(SqlTreeProperty[] props) { + public void add(STreeProperty[] props) { propsList.addAll(Arrays.asList(props)); } - public void add(SqlTreeProperty prop) { + public void add(STreeProperty prop) { propsList.add(prop); propNames.add(prop.getName()); } - public SqlTreeProperty[] getProps() { - return propsList.toArray(new SqlTreeProperty[propsList.size()]); + public STreeProperty[] getProps() { + return propsList.toArray(new STreeProperty[propsList.size()]); } boolean isPartialObject() { @@ -97,7 +97,7 @@ public class SqlTreeProperties { */ private String aggregationJoin() { if (!allProperties) { - for (SqlTreeProperty beanProperty : propsList) { + for (STreeProperty beanProperty : propsList) { if (beanProperty.isAggregation()) { aggregation = true; aggregationPath = beanProperty.getElPrefix(); diff --git a/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryDetail.java b/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryDetail.java index 6bdc89fd2..e7fa731b3 100644 --- a/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryDetail.java +++ b/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryDetail.java @@ -1,11 +1,11 @@ package io.ebeaninternal.server.querydefn; import io.ebean.FetchConfig; +import io.ebean.util.SplitName; import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeaninternal.server.deploy.BeanPropertyAssoc; import io.ebeaninternal.server.el.ElPropertyDeploy; import io.ebeaninternal.server.el.ElPropertyValue; -import io.ebean.util.SplitName; import javax.persistence.PersistenceException; import java.io.Serializable; @@ -427,7 +427,7 @@ public class OrmQueryDetail implements Serializable { for (OrmQueryProperties joinProps : fetchPaths.values()) { if (!joinProps.hasSelectClause()) { BeanDescriptor assocDesc = desc.getBeanDescriptor(joinProps.getPath()); - if (assocDesc.hasDefaultSelectClause()) { + if (assocDesc != null && assocDesc.hasDefaultSelectClause()) { fetch(joinProps.getPath(), assocDesc.getDefaultSelectClause(), joinProps.getFetchConfig()); } } diff --git a/src/main/java/io/ebeanservice/docstore/api/support/DocStructure.java b/src/main/java/io/ebeanservice/docstore/api/support/DocStructure.java index cad53baf2..e67e473f3 100644 --- a/src/main/java/io/ebeanservice/docstore/api/support/DocStructure.java +++ b/src/main/java/io/ebeanservice/docstore/api/support/DocStructure.java @@ -72,7 +72,7 @@ public class DocStructure { public void prepareMany(BeanDescriptor desc) { Set strings = embedded.keySet(); for (String prop : strings) { - BeanPropertyAssoc embProp = (BeanPropertyAssoc) desc.findBeanProperty(prop); + BeanPropertyAssoc embProp = (BeanPropertyAssoc) desc.findProperty(prop); if (embProp.isMany()) { prepare(prop, embProp); } diff --git a/src/test/java/io/ebeaninternal/server/deploy/FormulaPropertyPathTest.java b/src/test/java/io/ebeaninternal/server/deploy/FormulaPropertyPathTest.java index 9526619c9..d20581c0b 100644 --- a/src/test/java/io/ebeaninternal/server/deploy/FormulaPropertyPathTest.java +++ b/src/test/java/io/ebeaninternal/server/deploy/FormulaPropertyPathTest.java @@ -1,7 +1,7 @@ package io.ebeaninternal.server.deploy; import io.ebean.BaseTestCase; -import io.ebeaninternal.server.query.SqlTreeProperty; +import io.ebeaninternal.server.query.STreeProperty; import org.junit.Test; import org.tests.model.basic.Customer; @@ -76,7 +76,7 @@ public class FormulaPropertyPathTest extends BaseTestCase { assertThat(propertyPath.alias()).isNull(); } - SqlTreeProperty treeProperty = propertyPath.build(); + STreeProperty treeProperty = propertyPath.build(); assertThat(treeProperty).isNotNull(); } diff --git a/src/test/java/org/tests/basic/TestAnnotationBase.java b/src/test/java/org/tests/basic/TestAnnotationBase.java index e40c36cd9..f84ec99ab 100644 --- a/src/test/java/org/tests/basic/TestAnnotationBase.java +++ b/src/test/java/org/tests/basic/TestAnnotationBase.java @@ -1,13 +1,13 @@ package org.tests.basic; import io.ebean.BaseTestCase; +import io.ebean.annotation.Platform; import io.ebean.annotation.Where; import io.ebean.util.AnnotationUtil; -import io.ebean.annotation.Platform; import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeaninternal.server.deploy.BeanProperty; -import org.tests.model.basic.ValidationGroupSomething; import org.junit.Test; +import org.tests.model.basic.ValidationGroupSomething; import javax.persistence.Entity; import javax.validation.constraints.NotNull; @@ -125,23 +125,23 @@ public class TestAnnotationBase extends BaseTestCase { @Test - public void testFindMaxSize() throws NoSuchFieldException, SecurityException { + public void testFindMaxSize() throws SecurityException { BeanDescriptor descriptor = spiEbeanServer().getBeanDescriptor(TestAnnotationBaseEntity.class); - BeanProperty bp = descriptor.findBeanProperty("constraintAnnotation"); + BeanProperty bp = descriptor.findProperty("constraintAnnotation"); assertEquals(40, bp.getDbLength()); } @Test - public void testNotNullWithGroup() throws NoSuchFieldException, SecurityException { + public void testNotNullWithGroup() throws SecurityException { BeanDescriptor descriptor = spiEbeanServer().getBeanDescriptor(TestAnnotationBaseEntity.class); - BeanProperty bp = descriptor.findBeanProperty("null1"); + BeanProperty bp = descriptor.findProperty("null1"); assertFalse(bp.isNullable()); - bp = descriptor.findBeanProperty("null2"); + bp = descriptor.findProperty("null2"); assertTrue(bp.isNullable()); - bp = descriptor.findBeanProperty("null3"); + bp = descriptor.findProperty("null3"); assertTrue(bp.isNullable()); } diff --git a/src/test/java/org/tests/model/elementcollection/EcPerson.java b/src/test/java/org/tests/model/elementcollection/EcPerson.java new file mode 100644 index 000000000..00550b5c6 --- /dev/null +++ b/src/test/java/org/tests/model/elementcollection/EcPerson.java @@ -0,0 +1,69 @@ +package org.tests.model.elementcollection; + +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.Version; +import java.util.ArrayList; +import java.util.List; + +@Entity +public class EcPerson { + + @Id + long id; + + String name; + + @ElementCollection + @CollectionTable(name = "ec_person_phone", joinColumns = @JoinColumn(name = "owner_id", referencedColumnName = "id")) + @Column(name = "phone") + List phoneNumbers = new ArrayList<>(); + + @Version + long version; + + public EcPerson(String name) { + this.name = name; + } + + @Override + public String toString() { + return "person id:" + id + " name:" + name + " phs:" + phoneNumbers; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getPhoneNumbers() { + return phoneNumbers; + } + + public void setPhoneNumbers(List phoneNumbers) { + this.phoneNumbers = phoneNumbers; + } + + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } +} diff --git a/src/test/java/org/tests/model/elementcollection/TestElementCollectionBasic.java b/src/test/java/org/tests/model/elementcollection/TestElementCollectionBasic.java new file mode 100644 index 000000000..dd5ea2bc3 --- /dev/null +++ b/src/test/java/org/tests/model/elementcollection/TestElementCollectionBasic.java @@ -0,0 +1,51 @@ +package org.tests.model.elementcollection; + +import io.ebean.BaseTestCase; +import io.ebean.Ebean; +import org.junit.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestElementCollectionBasic extends BaseTestCase { + + @Test + public void test() { + + EcPerson person = new EcPerson("Fiona021"); + person.getPhoneNumbers().add("021 1234"); + person.getPhoneNumbers().add("021 4321"); + Ebean.save(person); + + + EcPerson person1 = new EcPerson("Fiona09"); + person1.getPhoneNumbers().add("09 1234"); + person1.getPhoneNumbers().add("09 4321"); + Ebean.save(person1); + + List found = + Ebean.find(EcPerson.class).where() + .startsWith("name", "Fiona0") + .order().asc("id") + .findList(); + + List phoneNumbers0 = found.get(0).getPhoneNumbers(); + List phoneNumbers1 = found.get(1).getPhoneNumbers(); + phoneNumbers0.size(); + + assertThat(phoneNumbers0).containsExactly("021 1234", "021 4321"); + assertThat(phoneNumbers1).containsExactly("09 1234", "09 4321"); + + + List found2 = + Ebean.find(EcPerson.class) + .fetch("phoneNumbers") + .where() + .startsWith("name", "Fiona0") + .order().asc("id") + .findList(); + + System.out.println(found2); + } +}