From 6b27a9d280663d1e7726dc0cbc9839459b50ec72 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Thu, 10 Jan 2019 03:40:02 +1300 Subject: [PATCH] Support @ManyToOne inside @Embeddables - fix for #52 (#1608) * #52 - @ManyToOne support inside @Embeddables Initial test and work * #52 - @ManyToOne support inside @Embeddables Initial test and work * #52 - @ManyToOne support inside @Embeddables Initial test and work * #52 - @ManyToOne support inside @Embeddables Initial test and work * #52 - @ManyToOne support inside @Embeddables Initial test and work * #52 - @ManyToOne support inside @Embeddables Initial test and work * #52 - @ManyToOne support inside @Embeddables Update test with sql capture * #52 - @ManyToOne support inside @Embeddables Update test with cache clearing before hand (such that we hit DB on lazy load) --- .../build/ModelBuildPropertyVisitor.java | 6 +- .../server/deploy/AssocOneHelp.java | 9 ++- .../server/deploy/AssocOneHelpRefSimple.java | 4 +- .../server/deploy/BeanDescriptor.java | 25 +++----- .../deploy/BeanDescriptorInitContext.java | 10 +++ .../server/deploy/BeanDescriptorManager.java | 64 +++++++++++-------- .../deploy/BeanEmbeddedMetaFactory.java | 8 ++- .../server/deploy/BeanPropertyAssoc.java | 16 +++++ .../server/deploy/BeanPropertyAssocOne.java | 31 +++++++-- .../server/deploy/TableJoin.java | 16 +++++ .../server/deploy/TableJoinColumn.java | 13 ++++ .../server/deploy/parse/AnnotationFields.java | 1 + .../server/deploy/parse/AnnotationParser.java | 1 + .../server/deploy/parse/DeployBeanInfo.java | 18 ++++++ .../dmlbind/FactoryBaseProperties.java | 2 +- .../persist/dmlbind/FactoryEmbedded.java | 6 +- .../persist/dmlbind/FactoryProperty.java | 24 ++++--- .../java/org/tests/model/embedded/EAddr.java | 57 +++++++++++++++++ .../org/tests/model/embedded/EPerAddr.java | 58 +++++++++++++++++ .../model/embedded/TestEmbeddedManyToOne.java | 56 ++++++++++++++++ 20 files changed, 356 insertions(+), 69 deletions(-) create mode 100644 src/test/java/org/tests/model/embedded/EAddr.java create mode 100644 src/test/java/org/tests/model/embedded/EPerAddr.java create mode 100644 src/test/java/org/tests/model/embedded/TestEmbeddedManyToOne.java 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 0e366a9b1..fa7950b45 100644 --- a/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java +++ b/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java @@ -147,7 +147,11 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor { @Override public void visitEmbeddedScalar(BeanProperty p, BeanPropertyAssocOne embedded) { - visitScalar(p); + if (p instanceof BeanPropertyAssocOne) { + visitOneImported((BeanPropertyAssocOne)p); + } else { + visitScalar(p); + } if (embedded.isId()) { // compound primary key lastColumn.setPrimaryKey(true); diff --git a/src/main/java/io/ebeaninternal/server/deploy/AssocOneHelp.java b/src/main/java/io/ebeaninternal/server/deploy/AssocOneHelp.java index 0fbf29da6..e41556a9f 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/AssocOneHelp.java +++ b/src/main/java/io/ebeaninternal/server/deploy/AssocOneHelp.java @@ -17,9 +17,16 @@ abstract class AssocOneHelp { protected final BeanDescriptor target; + private final String path; + AssocOneHelp(BeanPropertyAssocOne property) { + this(property, null); + } + + AssocOneHelp(BeanPropertyAssocOne property, String embeddedPrefix) { this.property = property; this.target = property.targetDescriptor; + this.path = (embeddedPrefix == null) ? property.name : embeddedPrefix + "." + property.name; } /** @@ -67,7 +74,7 @@ abstract class AssocOneHelp { boolean disableLazyLoading = ctx.isDisableLazyLoading(); Object ref = target.contextRef(pc, ctx.isReadOnly(), disableLazyLoading, id); if (!disableLazyLoading) { - ctx.register(property.name, ((EntityBean) ref)._ebean_getIntercept()); + ctx.register(path, ((EntityBean) ref)._ebean_getIntercept()); } return ref; } diff --git a/src/main/java/io/ebeaninternal/server/deploy/AssocOneHelpRefSimple.java b/src/main/java/io/ebeaninternal/server/deploy/AssocOneHelpRefSimple.java index 9e10e9ec8..967d7d492 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/AssocOneHelpRefSimple.java +++ b/src/main/java/io/ebeaninternal/server/deploy/AssocOneHelpRefSimple.java @@ -5,8 +5,8 @@ package io.ebeaninternal.server.deploy; */ class AssocOneHelpRefSimple extends AssocOneHelp { - AssocOneHelpRefSimple(BeanPropertyAssocOne property) { - super(property); + AssocOneHelpRefSimple(BeanPropertyAssocOne property, String embeddedPrefix) { + super(property, embeddedPrefix); } /** diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index 11b72808f..b94b16bc9 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -724,16 +724,9 @@ public class BeanDescriptor implements BeanType, STreeType { inheritInfo.setDescriptor(this); } - if (isEmbedded()) { - // initialise all the properties - for (BeanProperty prop : propertiesAll()) { - prop.initialise(initContext); - } - } else { - // initialise just the Id properties - if (idProperty != null) { - idProperty.initialise(initContext); - } + // initialise just the Id property only + if (idProperty != null) { + idProperty.initialise(initContext); } } @@ -759,14 +752,12 @@ public class BeanDescriptor implements BeanType, STreeType { } } - if (!isEmbedded()) { - // initialise all the non-id properties - for (BeanProperty prop : propertiesAll()) { - if (!prop.isId()) { - prop.initialise(initContext); - } - prop.registerColumn(this, null); + // initialise all the non-id properties + for (BeanProperty prop : propertiesAll()) { + if (!prop.isId()) { + prop.initialise(initContext); } + prop.registerColumn(this, null); } if (unidirectional != null) { diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorInitContext.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorInitContext.java index ef367199d..ec9c19044 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorInitContext.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorInitContext.java @@ -8,6 +8,8 @@ class BeanDescriptorInitContext { private final Map draftTables; private final String asOfViewSuffix; + private String embeddedPrefix; + BeanDescriptorInitContext(Map withHistoryTables, Map draftTables, String asOfViewSuffix) { this.withHistoryTables = withHistoryTables; this.draftTables = draftTables; @@ -29,4 +31,12 @@ class BeanDescriptorInitContext { void addDraftIntersection(String intersectionPublishTable, String intersectionDraftTable) { draftTables.put(intersectionPublishTable, intersectionDraftTable); } + + public void setEmbeddedPrefix(String embeddedPrefix) { + this.embeddedPrefix = embeddedPrefix; + } + + public String getEmbeddedPrefix() { + return embeddedPrefix; + } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java index ee8087a4f..359da02de 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java @@ -150,8 +150,6 @@ public class BeanDescriptorManager implements BeanDescriptorMap { private final String serverName; - private Map, DeployBeanInfo> deployInfoMap = new HashMap<>(); - private final Map, BeanTable> beanTableMap = new HashMap<>(); private final Map> descMap = new HashMap<>(); @@ -196,6 +194,12 @@ public class BeanDescriptorManager implements BeanDescriptorMap { private final int queryPlanTTLSeconds; + // temporary collections used during startup and then cleared + + private Map, DeployBeanInfo> deployInfoMap = new HashMap<>(); + private Set> embeddedIdTypes = new HashSet<>(); + private List> embeddedBeans = new ArrayList<>(); + /** * Create for a given database dbConfig. */ @@ -366,7 +370,6 @@ public class BeanDescriptorManager implements BeanDescriptorMap { createListeners(); readEntityDeploymentInitial(); readXmlMapping(mappings); - readEmbeddedDeployment(); readEntityBeanTable(); readEntityDeploymentAssociations(); readInheritedIdGenerators(); @@ -385,7 +388,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap { logStatus(); - deployInfoMap.clear(); + // clear collections we no longer need + embeddedIdTypes = null; + embeddedBeans = null; deployInfoMap = null; return asOfTableMap; @@ -592,8 +597,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap { for (BeanDescriptor d : descMap.values()) { d.initLast(); if (!d.isEmbedded()) { - BeanManager m = beanManagerFactory.create(d); - beanManagerMap.put(d.getFullName(), m); + beanManagerMap.put(d.getFullName(), beanManagerFactory.create(d)); checkForValidEmbeddedId(d); } } @@ -676,11 +680,6 @@ public class BeanDescriptorManager implements BeanDescriptorMap { logger.debug("Entities[{}]", entityBeanCount); } - private BeanDescriptor createEmbedded(Class beanClass) { - DeployBeanInfo info = getDeploy(beanClass); - return new BeanDescriptor<>(this, info.getDescriptor()); - } - /** * Return the bean deploy info for the given class. */ @@ -689,24 +688,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap { return (DeployBeanInfo) deployInfoMap.get(cls); } - private void registerBeanDescriptor(BeanDescriptor desc) { + private void registerBeanDescriptor(DeployBeanInfo info) { + BeanDescriptor desc = new BeanDescriptor<>(this, info.getDescriptor()); descMap.put(desc.getBeanType().getName(), desc); if (desc.isDocStoreMapped()) { descQueueMap.put(desc.getDocStoreQueueId(), desc); } } - /** - * Read deployment information for all the embedded beans. - */ - private void readEmbeddedDeployment() { - - List> embeddedClasses = bootupClasses.getEmbeddables(); - for (Class embeddedClass : embeddedClasses) { - registerBeanDescriptor(createEmbedded(embeddedClass)); - } - } - /** * Read the initial deployment information for the entities. *

@@ -719,14 +708,31 @@ public class BeanDescriptorManager implements BeanDescriptorMap { for (Class entityClass : bootupClasses.getEntities()) { DeployBeanInfo info = createDeployBeanInfo(entityClass); deployInfoMap.put(entityClass, info); + Class embeddedIdType = info.getEmbeddedIdType(); + if (embeddedIdType != null){ + embeddedIdTypes.add(embeddedIdType); + } } for (Class entityClass : bootupClasses.getEmbeddables()) { DeployBeanInfo info = createDeployBeanInfo(entityClass); - readDeployAssociations(info); deployInfoMap.put(entityClass, info); + if (embeddedIdTypes.contains(entityClass)) { + // register embeddedId types early - scalar properties only + // and needed for creating BeanTables (id properties) + registerEmbeddedBean(info); + } else { + // delay register of other embedded beans until after + // the BeanTables have been created to support ManyToOne + embeddedBeans.add(info); + } } } + private void registerEmbeddedBean(DeployBeanInfo info) { + readDeployAssociations(info); + registerBeanDescriptor(info); + } + /** * Create the BeanTable information which has the base table and id. *

@@ -739,6 +745,11 @@ public class BeanDescriptorManager implements BeanDescriptorMap { BeanTable beanTable = createBeanTable(info); beanTableMap.put(beanTable.getBeanType(), beanTable); } + + // register non-id embedded beans (after bean tables are created) + for (DeployBeanInfo info : embeddedBeans) { + registerEmbeddedBean(info); + } } /** @@ -816,13 +827,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap { secondaryPropsJoins(info); } - // Set inheritance info for (DeployBeanInfo info : deployInfoMap.values()) { setInheritanceInfo(info); } for (DeployBeanInfo info : deployInfoMap.values()) { - registerBeanDescriptor(new BeanDescriptor(this, info.getDescriptor())); + if (!info.isEmbedded()) { + registerBeanDescriptor(info); + } } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanEmbeddedMetaFactory.java b/src/main/java/io/ebeaninternal/server/deploy/BeanEmbeddedMetaFactory.java index cfc8e306f..11f287025 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanEmbeddedMetaFactory.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanEmbeddedMetaFactory.java @@ -30,7 +30,7 @@ public class BeanEmbeddedMetaFactory { String columnPrefix = prop.getColumnPrefix(); Map propColMap = prop.getDeployEmbedded().getPropertyColumnMap(); - BeanProperty[] sourceProperties = targetDesc.propertiesBaseScalar(); + BeanProperty[] sourceProperties = targetDesc.propertiesNonTransient(); BeanProperty[] embeddedProperties = new BeanProperty[sourceProperties.length]; for (int i = 0; i < sourceProperties.length; i++) { @@ -45,7 +45,11 @@ public class BeanEmbeddedMetaFactory { } BeanPropertyOverride overrides = new BeanPropertyOverride(dbColumn); - embeddedProperties[i] = new BeanProperty(sourceProperties[i], overrides); + if (sourceProperties[i] instanceof BeanPropertyAssocOne) { + embeddedProperties[i] = new BeanPropertyAssocOne((BeanPropertyAssocOne)sourceProperties[i], overrides); + } else { + embeddedProperties[i] = new BeanProperty(sourceProperties[i], overrides); + } } return new BeanEmbeddedMeta(embeddedProperties); diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java index f24d71346..782ee8043 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java @@ -104,6 +104,22 @@ public abstract class BeanPropertyAssoc extends BeanProperty implements STree this.fetchPreference = deploy.getFetchPreference(); } + /** + * Copy constructor for ManyToOne inside Embeddable. + */ + public BeanPropertyAssoc(BeanPropertyAssoc source, BeanPropertyOverride override) { + super(source, override); + foreignKey = source.foreignKey; + extraWhere = source.extraWhere; + beanTable = source.beanTable; + mappedBy = source.mappedBy; + docStoreDoc = source.docStoreDoc; + targetType = source.targetType; + cascadeInfo = source.cascadeInfo; + fetchPreference = source.fetchPreference; + tableJoin = source.tableJoin.withOverrideColumn(override.getDbColumn()); + } + /** * Initialise post construction. */ diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java index 4245235cd..a5ffe9413 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java @@ -93,14 +93,35 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc implements STr } } + /** + * Copy constructor for ManyToOne inside Embeddable. + */ + public BeanPropertyAssocOne(BeanPropertyAssocOne source, BeanPropertyOverride override) { + super(source, override); + primaryKeyExport = source.primaryKeyExport; + oneToOne = source.oneToOne; + oneToOneExported = source.oneToOneExported; + orphanRemoval = source.orphanRemoval; + embeddedProps = null; + embeddedPropsMap = null; + } + @Override public void initialise(BeanDescriptorInitContext initContext) { super.initialise(initContext); - initialiseAssocOne(); + initialiseAssocOne(initContext.getEmbeddedPrefix()); + if (embedded) { + // initialise ManyToOne importedId + initContext.setEmbeddedPrefix(name); + for (BeanProperty embeddedProp : embeddedProps) { + embeddedProp.initialise(initContext); + } + initContext.setEmbeddedPrefix(null); + } } - private void initialiseAssocOne() { - localHelp = createHelp(embedded, oneToOneExported); + private void initialiseAssocOne(String embeddedPrefix) { + localHelp = createHelp(embedded, oneToOneExported, embeddedPrefix); if (!isTransient) { //noinspection StatementWithEmptyBody @@ -668,7 +689,7 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc implements STr } } - private AssocOneHelp createHelp(boolean embedded, boolean oneToOneExported) { + private AssocOneHelp createHelp(boolean embedded, boolean oneToOneExported, String embeddedPrefix) { if (embedded) { return new AssocOneHelpEmbedded(this); } else if (oneToOneExported) { @@ -677,7 +698,7 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc implements STr if (targetInheritInfo != null) { return new AssocOneHelpRefInherit(this); } else { - return new AssocOneHelpRefSimple(this); + return new AssocOneHelpRefSimple(this, embeddedPrefix); } } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/TableJoin.java b/src/main/java/io/ebeaninternal/server/deploy/TableJoin.java index eedb2b547..03cb35d6c 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/TableJoin.java +++ b/src/main/java/io/ebeaninternal/server/deploy/TableJoin.java @@ -51,6 +51,15 @@ public final class TableJoin { this.queryHash = calcQueryHash(); } + private TableJoin(TableJoin source, String overrideColumn) { + this.table = source.table; + this.type = source.type; + this.inheritInfo = source.inheritInfo; + this.columns = new TableJoinColumn[1]; + this.columns[0] = source.columns[0].withOverrideColumn(overrideColumn); + this.queryHash = calcQueryHash(); + } + /** * Calculate a hash value for adding to a query plan. */ @@ -166,4 +175,11 @@ public final class TableJoin { sb.append(a2).append(".").append(pair.getForeignDbColumn()); } } + + TableJoin withOverrideColumn(String overrideColumn) { + if (columns.length == 1 && overrideColumn != null && !overrideColumn.equals(columns[0].getLocalDbColumn())) { + return new TableJoin(this, overrideColumn); + } + return this; + } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/TableJoinColumn.java b/src/main/java/io/ebeaninternal/server/deploy/TableJoinColumn.java index f8921b49e..a8209d3ff 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/TableJoinColumn.java +++ b/src/main/java/io/ebeaninternal/server/deploy/TableJoinColumn.java @@ -46,6 +46,16 @@ public class TableJoinColumn { this.queryHash = hash(); } + private TableJoinColumn(TableJoinColumn source, String overrideColumn) { + this.localDbColumn = InternString.intern(overrideColumn); + this.foreignDbColumn = source.foreignDbColumn; + this.localSqlFormula = null; + this.foreignSqlFormula = null; + this.insertable = source.isInsertable(); + this.updateable = source.isUpdateable(); + this.queryHash = hash(); + } + int hash() { int result = localDbColumn != null ? localDbColumn.hashCode() : 0; result = 92821 * result + (foreignDbColumn != null ? foreignDbColumn.hashCode() : 0); @@ -123,4 +133,7 @@ public class TableJoinColumn { return foreignSqlFormula; } + TableJoinColumn withOverrideColumn(String overrideColumn) { + return new TableJoinColumn(this, overrideColumn); + } } 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 4b1b28b69..8c2f48754 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java @@ -132,6 +132,7 @@ public class AnnotationFields extends AnnotationParser { prop.setId(); prop.setNullable(false); prop.setEmbedded(); + info.setEmbeddedId(prop); } DocEmbedded docEmbedded = get(prop, DocEmbedded.class); 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 1bcb1faf3..e8b489a23 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationParser.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationParser.java @@ -54,6 +54,7 @@ public abstract class AnnotationParser extends AnnotationBase { } else { prop.setId(); prop.setEmbedded(); + info.setEmbeddedId(prop); } } 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 d683578bc..3b577d986 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/DeployBeanInfo.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/DeployBeanInfo.java @@ -3,6 +3,7 @@ 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.DeployBeanPropertyAssoc; import io.ebeaninternal.server.deploy.meta.DeployTableJoin; import io.ebeaninternal.server.query.SqlJoinType; import io.ebeaninternal.server.rawsql.SpiRawSql; @@ -23,6 +24,8 @@ public class DeployBeanInfo { private final DeployBeanDescriptor descriptor; + private DeployBeanPropertyAssoc embeddedId; + /** * Create with a DeployUtil and BeanDescriptor. */ @@ -89,4 +92,19 @@ public class DeployBeanInfo { public void setPrimaryKeyJoin(TableJoin join) { descriptor.setPrimaryKeyJoin(join); } + + /** + * This bean type has an embedded Id property. + */ + public void setEmbeddedId(DeployBeanPropertyAssoc embeddedId) { + this.embeddedId = embeddedId; + } + + public Class getEmbeddedIdType() { + return (embeddedId == null) ? null : embeddedId.getTargetType(); + } + + public boolean isEmbedded() { + return descriptor.isEmbedded(); + } } diff --git a/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryBaseProperties.java b/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryBaseProperties.java index 36c0998b8..43b9bec8d 100644 --- a/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryBaseProperties.java +++ b/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryBaseProperties.java @@ -28,7 +28,7 @@ public class FactoryBaseProperties { for (BeanProperty prop : desc.propertiesBaseScalar()) { if (!prop.isImportedPrimaryKey()) { - Bindable item = factoryProperty.create(prop, mode, withLobs); + Bindable item = factoryProperty.create(prop, mode, withLobs, false); if (item != null) { list.add(item); } diff --git a/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryEmbedded.java b/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryEmbedded.java index 585bf1292..380c7b3a9 100644 --- a/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryEmbedded.java +++ b/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryEmbedded.java @@ -24,13 +24,11 @@ public class FactoryEmbedded { */ public void create(List list, BeanDescriptor desc, DmlMode mode, boolean withLobs) { - BeanPropertyAssocOne[] embedded = desc.propertiesEmbedded(); - - for (BeanPropertyAssocOne anEmbedded : embedded) { + for (BeanPropertyAssocOne anEmbedded : desc.propertiesEmbedded()) { BeanProperty[] props = anEmbedded.getProperties(); List bindList = new ArrayList<>(props.length); for (BeanProperty prop : props) { - Bindable item = factoryProperty.create(prop, mode, withLobs); + Bindable item = factoryProperty.create(prop, mode, withLobs, true); if (item != null) { bindList.add(item); } diff --git a/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryProperty.java b/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryProperty.java index b0d0b8d4b..5adf5124c 100644 --- a/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryProperty.java +++ b/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryProperty.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.persist.dmlbind; import io.ebeaninternal.server.deploy.BeanProperty; +import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; import io.ebeaninternal.server.persist.dml.DmlMode; /** @@ -14,14 +15,14 @@ public class FactoryProperty { private final boolean bindEncryptDataFirst; - public FactoryProperty(boolean bindEncryptDataFirst) { + FactoryProperty(boolean bindEncryptDataFirst) { this.bindEncryptDataFirst = bindEncryptDataFirst; } /** * Create a Bindable for the property given the mode and withLobs flag. */ - public Bindable create(BeanProperty prop, DmlMode mode, boolean withLobs) { + public Bindable create(BeanProperty prop, DmlMode mode, boolean withLobs, boolean allowManyToOne) { if (DmlMode.INSERT == mode && !prop.isDbInsertable()) { return null; @@ -30,15 +31,18 @@ public class FactoryProperty { return null; } - if (prop.isLob()) { - if (!withLobs) { - // Lob exclusion - return null; - } else { - return prop.isDbEncrypted() ? new BindableEncryptedProperty(prop, bindEncryptDataFirst) : new BindableProperty(prop); - } + if (prop.isLob() && !withLobs) { + // Lob exclusion + return null; + } + if (prop.isDbEncrypted()){ + return new BindableEncryptedProperty(prop, bindEncryptDataFirst); } - return prop.isDbEncrypted() ? new BindableEncryptedProperty(prop, bindEncryptDataFirst) : new BindableProperty(prop); + if (allowManyToOne && prop instanceof BeanPropertyAssocOne) { + return new BindableAssocOne((BeanPropertyAssocOne)prop); + } + + return new BindableProperty(prop); } } diff --git a/src/test/java/org/tests/model/embedded/EAddr.java b/src/test/java/org/tests/model/embedded/EAddr.java new file mode 100644 index 000000000..5dc697ddd --- /dev/null +++ b/src/test/java/org/tests/model/embedded/EAddr.java @@ -0,0 +1,57 @@ +package org.tests.model.embedded; + +import org.tests.model.basic.Country; + +import javax.persistence.Embeddable; +import javax.persistence.ManyToOne; + +@Embeddable +public class EAddr { + + String street; + + String suburb; + + String city; + + @ManyToOne + Country country; + + public EAddr(String street, String city, Country country) { + this.street = street; + this.city = city; + this.country = country; + } + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + public String getSuburb() { + return suburb; + } + + public void setSuburb(String suburb) { + this.suburb = suburb; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public Country getCountry() { + return country; + } + + public void setCountry(Country country) { + this.country = country; + } +} diff --git a/src/test/java/org/tests/model/embedded/EPerAddr.java b/src/test/java/org/tests/model/embedded/EPerAddr.java new file mode 100644 index 000000000..b3d4ca3e3 --- /dev/null +++ b/src/test/java/org/tests/model/embedded/EPerAddr.java @@ -0,0 +1,58 @@ +package org.tests.model.embedded; + +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Version; + +@Entity +public class EPerAddr { + + @Id + Long id; + + @Version + Long version; + + String name; + + @Embedded(prefix = "ma_") + EAddr address; + + public EPerAddr(String name, EAddr address) { + this.name = name; + this.address = address; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getVersion() { + return version; + } + + public void setVersion(Long version) { + this.version = version; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public EAddr getAddress() { + return address; + } + + public void setAddress(EAddr address) { + this.address = address; + } +} diff --git a/src/test/java/org/tests/model/embedded/TestEmbeddedManyToOne.java b/src/test/java/org/tests/model/embedded/TestEmbeddedManyToOne.java new file mode 100644 index 000000000..b7d04754c --- /dev/null +++ b/src/test/java/org/tests/model/embedded/TestEmbeddedManyToOne.java @@ -0,0 +1,56 @@ +package org.tests.model.embedded; + +import io.ebean.BaseTestCase; +import io.ebean.Ebean; +import io.ebean.plugin.BeanType; +import io.ebean.plugin.Property; +import org.ebeantest.LoggedSqlCollector; +import org.junit.Test; +import org.tests.model.basic.Country; +import org.tests.model.basic.ResetBasicData; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestEmbeddedManyToOne extends BaseTestCase { + + @Test + public void test() { + + ResetBasicData.reset(); + + BeanType embType = Ebean.getDefaultServer().getPluginApi().getBeanType(EAddr.class); + + Ebean.getDefaultServer().getServerCacheManager().clearAll(); + + Country nz = Ebean.getReference(Country.class, "NZ"); + + EAddr addr = new EAddr("Foo", "Bar", nz); + EPerAddr perAddr = new EPerAddr("Embed", addr); + + Property country = embType.getProperty("country"); + Object val = country.getVal(addr); + + assertThat(val).isSameAs(nz); + + Ebean.save(perAddr); + + + LoggedSqlCollector.start(); + + EPerAddr found = Ebean.find(EPerAddr.class, perAddr.getId()); + + assertThat(found.getAddress().getCountry().getCode()).isEqualTo("NZ"); + assertThat(found.getAddress().getCountry().getName()).startsWith("New"); + + + List sql = LoggedSqlCollector.stop(); + assertThat(sql).hasSize(2); + + if (isH2()) { + assertThat(sql.get(0)).contains("select t0.id, t0.name, t0.version, t0.ma_street, t0.ma_suburb, t0.ma_city, t0.ma_country_code from eper_addr t0 where t0.id = ? "); + assertThat(sql.get(1)).contains("select t0.code, t0.name from o_country t0 where t0.code = ? ; --bind(NZ,"); + } + } +}