diff --git a/src/main/java/io/ebean/bean/EntityBeanIntercept.java b/src/main/java/io/ebean/bean/EntityBeanIntercept.java index cde09876f..e35384ad4 100644 --- a/src/main/java/io/ebean/bean/EntityBeanIntercept.java +++ b/src/main/java/io/ebean/bean/EntityBeanIntercept.java @@ -133,6 +133,20 @@ public final class EntityBeanIntercept implements Serializable { this.nodeUsageCollector = usageCollector; } + /** + * Return the ownerId (IdClass). + */ + public Object getOwnerId() { + return ownerId; + } + + /** + * Set the ownerId (IdClass). + */ + public void setOwnerId(Object ownerId) { + this.ownerId = ownerId; + } + /** * Return the owning bean for an embedded bean. */ diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java index d1d449120..6bd12fda2 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java @@ -1350,7 +1350,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap { // already assigned (So custom or UUID) return; } - if (desc.propertiesId().isEmpty()) { + if (desc.idProperty() == null) { // bean doesn't have an Id property if (desc.isBaseTableType() && desc.getBeanFinder() == null) { // expecting an id property @@ -1491,6 +1491,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap { private boolean isPersistentField(DeployBeanProperty prop) { Field field = prop.getField(); + if (field == null) { + return false; + } int modifiers = field.getModifiers(); return !(Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers)) && !field.isAnnotationPresent(Transient.class); } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java b/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java index 85f66e177..a7260f269 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java @@ -61,10 +61,12 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty private static final Logger logger = LoggerFactory.getLogger(BeanProperty.class); /** - * Flag to mark this at part of the unique id. + * Flag to mark this is the id property. */ final boolean id; + final boolean importedPrimaryKey; + /** * Flag to make this as a dummy property for unidirecitonal relationships. */ @@ -274,6 +276,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty this.name = InternString.intern(deploy.getName()); this.propertyIndex = deploy.getPropertyIndex(); this.unidirectionalShadow = deploy.isUndirectionalShadow(); + this.importedPrimaryKey = deploy.isImportedPrimaryKey(); this.discriminator = deploy.isDiscriminator(); this.localEncrypted = deploy.isLocalEncrypted(); this.dbEncrypted = deploy.isDbEncrypted(); @@ -395,6 +398,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty this.softDeleteDbSet = source.softDeleteDbSet; this.softDeleteDbPredicate = source.softDeleteDbPredicate; this.fetchEager = source.fetchEager; + this.importedPrimaryKey = source.importedPrimaryKey; this.unidirectionalShadow = source.unidirectionalShadow; this.discriminator = source.discriminator; this.localEncrypted = source.isLocalEncrypted(); @@ -473,8 +477,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty this.deployOrder = deployOrder; } - public ElPropertyValue buildElPropertyValue(String propName, String remainder, ElPropertyChainBuilder chain, - boolean propertyDeploy) { + public ElPropertyValue buildElPropertyValue(String propName, String remainder, ElPropertyChainBuilder chain, boolean propertyDeploy) { return null; } @@ -935,6 +938,13 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty return null; } + /** + * If true this bean maps to the primary key. + */ + public boolean isImportedPrimaryKey() { + return importedPrimaryKey; + } + @Override public boolean isAssocMany() { // Returns false - override in BeanPropertyAssocMany. diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java index 305ea9aa4..2d34242b1 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java @@ -40,8 +40,6 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc { private final boolean orphanRemoval; - private final boolean importedPrimaryKey; - private final boolean primaryKeyExport; private final PropertyForeignKey foreignKey; @@ -76,7 +74,6 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc { foreignKey = deploy.getForeignKey(); primaryKeyExport = deploy.isPrimaryKeyExport(); - importedPrimaryKey = deploy.isImportedPrimaryKey(); oneToOne = deploy.isOneToOne(); oneToOneExported = deploy.isOneToOneExported(); orphanRemoval = deploy.isOrphanRemoval(); @@ -346,13 +343,6 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc { return orphanRemoval; } - /** - * If true this bean maps to the primary key. - */ - public boolean isImportedPrimaryKey() { - return importedPrimaryKey; - } - @Override public void diff(String prefix, Map map, EntityBean newBean, EntityBean oldBean) { @@ -626,6 +616,14 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc { } } + @Override + public void setValueIntercept(EntityBean bean, Object value) { + super.setValueIntercept(bean, value); + if (embedded && value instanceof EntityBean) { + setEmbeddedOwner(bean, value); + } + } + /** * For embedded bean set the owner and all properties to be loaded (recursively). */ @@ -638,18 +636,10 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc { } } - private void setEmbeddedOwner(EntityBean bean, Object value) { + void setEmbeddedOwner(EntityBean bean, Object value) { ((EntityBean) value)._ebean_getIntercept().setEmbeddedOwner(bean, propertyIndex); } - @Override - public void setValueIntercept(EntityBean bean, Object value) { - super.setValueIntercept(bean, value); - if (embedded && value instanceof EntityBean) { - setEmbeddedOwner(bean, value); - } - } - @Override public void loadIgnore(DbReadContext ctx) { localHelp.loadIgnore(ctx); @@ -708,10 +698,10 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc { if (embedded) { writeJson.writeFieldName(name); BeanDescriptor refDesc = descriptor.getBeanDescriptor(value.getClass()); - refDesc.jsonWriteForInsert(writeJson, (EntityBean)value); + refDesc.jsonWriteForInsert(writeJson, (EntityBean) value); } else { - jsonWriteTargetId(writeJson, (EntityBean)value); + jsonWriteTargetId(writeJson, (EntityBean) value); } } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyIdClass.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyIdClass.java new file mode 100644 index 000000000..bd8486b39 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyIdClass.java @@ -0,0 +1,35 @@ +package io.ebeaninternal.server.deploy; + +import io.ebean.bean.EntityBean; +import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne; + +/** + * Bean property for an IdClass embeddedId. + */ +public class BeanPropertyIdClass extends BeanPropertyAssocOne { + + public BeanPropertyIdClass(BeanDescriptorMap owner, BeanDescriptor descriptor, DeployBeanPropertyAssocOne deploy) { + super(owner, descriptor, deploy); + } + + @Override + public void setValue(EntityBean bean, Object value) { + bean._ebean_getIntercept().setOwnerId(value); + } + + @Override + public void setValueIntercept(EntityBean bean, Object value) { + bean._ebean_getIntercept().setOwnerId(value); + } + + @Override + public Object getValue(EntityBean bean) { + return bean._ebean_getIntercept().getOwnerId(); + } + + @Override + public Object getValueIntercept(EntityBean bean) { + return bean._ebean_getIntercept().getOwnerId(); + } + +} diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanTable.java b/src/main/java/io/ebeaninternal/server/deploy/BeanTable.java index d9e3e1413..c481660e8 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanTable.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanTable.java @@ -30,7 +30,7 @@ public class BeanTable { */ private final String baseTable; - private final BeanProperty[] idProperties; + private final BeanProperty idProperty; /** * Create the BeanTable. @@ -39,7 +39,7 @@ public class BeanTable { this.owner = owner; this.beanType = mutable.getBeanType(); this.baseTable = InternString.intern(mutable.getBaseTable()); - this.idProperties = mutable.createIdProperties(owner); + this.idProperty = mutable.createIdProperty(owner); } @Override @@ -69,8 +69,8 @@ public class BeanTable { /** * Return the Id properties. */ - public BeanProperty[] getIdProperties() { - return idProperties; + public BeanProperty getIdProperty() { + return idProperty; } /** @@ -82,52 +82,51 @@ public class BeanTable { public void createJoinColumn(String foreignKeyPrefix, DeployTableJoin join, boolean reverse, String sqlFormulaSelect) { - boolean complexKey = false; - BeanProperty[] props = idProperties; - - if (idProperties.length == 1) { - if (idProperties[0] instanceof BeanPropertyAssocOne) { - BeanPropertyAssocOne assocOne = (BeanPropertyAssocOne) idProperties[0]; - props = assocOne.getProperties(); - complexKey = true; - } + if (idProperty == null) { + return; } - for (BeanProperty prop : props) { + if (idProperty instanceof BeanPropertyAssocOne) { + BeanPropertyAssocOne assocOne = (BeanPropertyAssocOne) idProperty; + BeanProperty[] props = assocOne.getProperties(); + for (BeanProperty prop : props) { + addToJoin(foreignKeyPrefix, join, reverse, sqlFormulaSelect, true, prop); + } + } else { + addToJoin(foreignKeyPrefix, join, reverse, sqlFormulaSelect, false, idProperty); + } + } - String lc = prop.getDbColumn(); - String fk = lc; - if (foreignKeyPrefix != null) { - fk = owner.getNamingConvention().getForeignKey(foreignKeyPrefix, fk); - } + private void addToJoin(String foreignKeyPrefix, DeployTableJoin join, boolean reverse, String sqlFormulaSelect, boolean complexKey, BeanProperty prop) { - if (complexKey) { - // just to copy the column name rather than prefix with the foreignKeyPrefix. - // I think that with complex keys this is the more common approach. - logger.debug("On table[{}] foreign key column [{}]", baseTable, lc); - fk = lc; - } - if (sqlFormulaSelect != null) { - fk = sqlFormulaSelect; - } - - DeployTableJoinColumn joinCol = new DeployTableJoinColumn(lc, fk); - joinCol.setForeignSqlFormula(sqlFormulaSelect); - if (reverse) { - joinCol = joinCol.reverse(); - } - join.addJoinColumn(joinCol); + String lc = prop.getDbColumn(); + String fk = lc; + if (foreignKeyPrefix != null) { + fk = owner.getNamingConvention().getForeignKey(foreignKeyPrefix, fk); } + if (complexKey) { + // just to copy the column name rather than prefix with the foreignKeyPrefix. + // I think that with complex keys this is the more common approach. + logger.debug("On table[{}] foreign key column [{}]", baseTable, lc); + fk = lc; + } + if (sqlFormulaSelect != null) { + fk = sqlFormulaSelect; + } + + DeployTableJoinColumn joinCol = new DeployTableJoinColumn(lc, fk); + joinCol.setForeignSqlFormula(sqlFormulaSelect); + if (reverse) { + joinCol = joinCol.reverse(); + } + join.addJoinColumn(joinCol); } /** * Return the primary key DB column. */ public String getIdColumn() { - if (idProperties.length != 1) { - throw new IllegalStateException("Expecting only one Id column to join to on "+beanType); - } - return idProperties[0].dbColumn; + return idProperty.dbColumn; } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java index 1df0d8ffd..1855dee14 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java +++ b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java @@ -15,7 +15,6 @@ import io.ebean.event.BeanPostLoad; import io.ebean.event.BeanQueryAdapter; import io.ebean.event.changelog.ChangeLogFilter; import io.ebean.text.PathProperties; -import io.ebean.util.CamelCaseHelper; import io.ebeaninternal.api.ConcurrencyMode; import io.ebeaninternal.server.core.CacheOptions; import io.ebeaninternal.server.deploy.BeanDescriptor.EntityType; @@ -91,6 +90,10 @@ public class DeployBeanDescriptor { */ private IdType idType; + private Class idClass; + + private DeployBeanPropertyAssocOne idClassProperty; + /** * Set to true if the identity is default for the platform. */ @@ -205,7 +208,7 @@ public class DeployBeanDescriptor { private DocStoreMode docStoreUpdate; private DocStoreMode docStoreDelete; - private List idProperties; + private DeployBeanProperty idProperty; private TableJoin primaryKeyJoin; private short profileId; @@ -219,6 +222,20 @@ public class DeployBeanDescriptor { this.beanType = beanType; } + /** + * Set the IdClass to use. + */ + public void setIdClass(Class idClass) { + this.idClass = idClass; + } + + /** + * Return true if there is a IdClass set. + */ + public boolean isIdClass() { + return idClass != null; + } + /** * PK is also a FK. */ @@ -335,7 +352,7 @@ public class DeployBeanDescriptor { DeployBeanTable beanTable = new DeployBeanTable(getBeanType()); beanTable.setBaseTable(baseTable); - beanTable.setIdProperties(propertiesId()); + beanTable.setIdProperty(idProperty()); return beanTable; } @@ -443,6 +460,10 @@ public class DeployBeanDescriptor { return cacheOptions; } + public DeployBeanPropertyAssocOne getIdClassProperty() { + return idClassProperty; + } + public DeployBeanPropertyAssocOne getUnidirectional() { return unidirectional; } @@ -684,6 +705,15 @@ public class DeployBeanDescriptor { } } + public void postAnnotations() { + if (idClass != null) { + idClassProperty = new DeployBeanPropertyAssocOne<>(this, idClass); + idClassProperty.setName("_idClass"); + idClassProperty.setEmbedded(); + idClassProperty.setNullable(false); + } + } + /** * Add a bean property. */ @@ -691,32 +721,8 @@ public class DeployBeanDescriptor { return propMap.put(prop.getName(), prop); } - /** - * Find the matching property for a given property name or dbColumn. - *

- * This is primarily to find imported primary key columns (ManyToOne that also match the PK). - *

- */ - DeployBeanProperty findMatch(String propertyName, String dbColumn) { - - DeployBeanProperty prop = propMap.get(propertyName); - if (prop != null) { - return prop; - } - if (dbColumn != null) { - String asCamel = CamelCaseHelper.toCamelFromUnderscore(dbColumn); - prop = propMap.get(asCamel); - if (prop != null) { - return prop; - } - // scan looking for dbColumn match - for (DeployBeanProperty property : propMap.values()) { - if (dbColumn.equals(property.getDbColumn())) { - return property; - } - } - } - return null; + public Collection properties() { + return propMap.values(); } /** @@ -935,16 +941,14 @@ public class DeployBeanDescriptor { */ public boolean isPrimaryKeyCompoundOrNonNumeric() { - List ids = propertiesId(); - if (ids.size() != 1) { - // compound key - return true; + DeployBeanProperty id = idProperty(); + if (id == null) { + return false; } - DeployBeanProperty p = ids.get(0); - if (p instanceof DeployBeanPropertyAssocOne) { - return ((DeployBeanPropertyAssocOne) p).isCompound(); + if (id instanceof DeployBeanPropertyAssocOne) { + return ((DeployBeanPropertyAssocOne) id).isCompound(); } else { - return !p.isDbNumberType(); + return !id.isDbNumberType(); } } @@ -953,37 +957,32 @@ public class DeployBeanDescriptor { * compound). This is for the purpose of defining a sequence name. */ public String getSinglePrimaryKeyColumn() { - List ids = propertiesId(); - if (ids.size() == 1) { - DeployBeanProperty p = ids.get(0); - if (p instanceof DeployBeanPropertyAssoc) { + DeployBeanProperty id = idProperty(); + if (id != null) { + if (id instanceof DeployBeanPropertyAssoc) { // its a compound primary key return null; } else { - return p.getDbColumn(); + return id.getDbColumn(); } } return null; } /** - * Return the BeanProperty that make up the unique id. - *

- * The order of these properties can be relied on to be consistent if the bean - * itself doesn't change or the xml deployment order does not change. - *

+ * Return the BeanProperty that is the Id. */ - public List propertiesId() { - - if (idProperties == null) { - idProperties = new ArrayList<>(2); - for (DeployBeanProperty prop : propMap.values()) { - if (prop.isId()) { - idProperties.add(prop); - } + public DeployBeanProperty idProperty() { + if (idProperty != null) { + return idProperty; + } + for (DeployBeanProperty prop : propMap.values()) { + if (prop.isId()) { + idProperty = prop; + return idProperty; } } - return idProperties; + return null; } public DeployBeanPropertyAssocOne findJoinToTable(String tableName) { 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 a363fdbdd..ce091b2ec 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java +++ b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java @@ -51,6 +51,7 @@ public class DeployBeanProperty { private static final int AUDITCOLUMN_ORDER = -1000000; private static final int VERSIONCOLUMN_ORDER = -1000000; private static final Set> PRIMITIVE_NUMBER_TYPES = new HashSet<>(); + static { PRIMITIVE_NUMBER_TYPES.add(float.class); PRIMITIVE_NUMBER_TYPES.add(double.class); @@ -64,6 +65,8 @@ public class DeployBeanProperty { */ private boolean id; + boolean importedPrimaryKey; + /** * Flag to mark the property as embedded. This could be on * BeanPropertyAssocOne rather than here. Put it here for checking Id type @@ -611,6 +614,16 @@ public class DeployBeanProperty { this.dbUpdateable = false; } + public void setImportedPrimaryKey() { + this.importedPrimaryKey = true; + } + + /** + * Set to true if this is part of the primary key. + */ + public void setImportedPrimaryKeyColumn(DeployBeanProperty primaryKey) { + this.importedPrimaryKey = true; + } public boolean isAggregation() { return aggregation != null; @@ -889,6 +902,13 @@ public class DeployBeanProperty { return genericType; } + /** + * Return true if this is part of the primary key. + */ + public boolean isImportedPrimaryKey() { + return importedPrimaryKey; + } + /** * Return true if this is included in the unique id. */ @@ -1046,6 +1066,10 @@ public class DeployBeanProperty { return tenantId; } + public boolean isIdClass() { + return desc.isIdClass(); + } + public void addDbMigrationInfo(DbMigrationInfo info) { if (dbMigrationInfos == null) { dbMigrationInfos = new ArrayList<>(); 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 021f5e2f8..ed26cc045 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocOne.java +++ b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocOne.java @@ -17,8 +17,6 @@ public class DeployBeanPropertyAssocOne extends DeployBeanPropertyAssoc { private boolean primaryKeyExport; - private boolean importedPrimaryKey; - private DeployBeanEmbedded deployEmbedded; private String columnPrefix; @@ -92,17 +90,11 @@ public class DeployBeanPropertyAssocOne extends DeployBeanPropertyAssoc { this.oneToOneExported = true; } - /** - * Return true if this is part of the primary key. - */ - public boolean isImportedPrimaryKey() { - return importedPrimaryKey; - } - /** * Set to true if this is part of the primary key. */ - void setImportedPrimaryKey(DeployBeanProperty primaryKey) { + @Override + public void setImportedPrimaryKeyColumn(DeployBeanProperty primaryKey) { this.importedPrimaryKey = true; String dbColumn = primaryKey.getDbColumn(); if (dbColumn != null) { diff --git a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java index 8c849e76b..241c56484 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java +++ b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java @@ -5,6 +5,7 @@ import io.ebeaninternal.server.deploy.BeanDescriptorMap; import io.ebeaninternal.server.deploy.BeanProperty; import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import io.ebeaninternal.server.deploy.BeanPropertyIdClass; import io.ebeaninternal.server.deploy.BeanPropertyOrderColumn; import io.ebeaninternal.server.deploy.BeanPropertySimpleCollection; import io.ebeaninternal.server.deploy.InheritInfo; @@ -39,7 +40,7 @@ public class DeployBeanPropertyLists { private final LinkedHashMap propertyMap; - private final List ids = new ArrayList<>(); + private BeanProperty id; private final List local = new ArrayList<>(); @@ -70,7 +71,13 @@ public class DeployBeanPropertyLists { public DeployBeanPropertyLists(BeanDescriptorMap owner, BeanDescriptor desc, DeployBeanDescriptor deploy) { this.desc = desc; - setImportedPrimaryKeys(deploy); + DeployBeanPropertyAssocOne deployId = deploy.getIdClassProperty(); + if (deployId != null) { + this.id = new BeanPropertyIdClass(owner, desc, deployId); + setImportedPrimaryKeysFor(deploy, deployId); + } else { + setImportedPrimaryKeys(deploy); + } DeployBeanProperty deployOrderColumn = deploy.getOrderColumn(); this.orderColumn = deployOrderColumn != null ? new BeanPropertyOrderColumn(desc, deployOrderColumn) : null; @@ -142,23 +149,43 @@ public class DeployBeanPropertyLists { *

*/ private void setImportedPrimaryKeys(DeployBeanDescriptor deploy) { + DeployBeanProperty id = deploy.idProperty(); + if (id instanceof DeployBeanPropertyAssocOne) { + setImportedPrimaryKeysFor(deploy, (DeployBeanPropertyAssocOne) id); + } + } - List ids = deploy.propertiesId(); - if (ids.size() == 1) { - DeployBeanProperty id = ids.get(0); - if (id instanceof DeployBeanPropertyAssocOne) { - // only interested if the primary key is a compound key - DeployBeanDescriptor targetDeploy = ((DeployBeanPropertyAssocOne) id).getTargetDeploy(); - for (DeployBeanPropertyAssocOne assoc : deploy.propertiesAssocOne()) { - DeployBeanProperty pkMatch = targetDeploy.findMatch(assoc.getName(), assoc.getDbColumn()); - if (pkMatch != null) { - assoc.setImportedPrimaryKey(pkMatch); - } - } + private void setImportedPrimaryKeysFor(DeployBeanDescriptor deploy, DeployBeanPropertyAssocOne id) { + + for (DeployBeanProperty prop : id.getTargetDeploy().properties()) { + DeployBeanProperty match = findImported(deploy, prop); + if (match != null) { + match.setImportedPrimaryKeyColumn(prop); } } } + private DeployBeanProperty findImported(DeployBeanDescriptor deploy, DeployBeanProperty embeddedScalar) { + + // the logical name and db column we are looking for a match on + String name = embeddedScalar.getName(); + String dbColumn = embeddedScalar.getDbColumn(); + + DeployBeanProperty match = deploy.getBeanProperty(name); + if (match != null) { + return match; + } + // could look to match more by dbColumn + + for (DeployBeanPropertyAssocOne assocOne : deploy.propertiesAssocOne()) { + if (name.equals(assocOne.getName()) || (dbColumn != null && dbColumn.equals(assocOne.getDbColumn()))) { + return assocOne; + } + } + + return null; + } + /** * Return the unidirectional. */ @@ -188,12 +215,13 @@ public class DeployBeanPropertyLists { return; } if (prop.isId()) { - ids.add(prop); + if (id != null) { + throw new IllegalStateException("More that one @Id property on " + desc.getFullName() + " ?"); + } + id = prop; return; - } else { - nonTransients.add(prop); } - + nonTransients.add(prop); if (prop.isMutableScalarType()) { mutable.add(prop); } @@ -255,15 +283,7 @@ public class DeployBeanPropertyLists { } public BeanProperty getId() { - if (ids.size() > 1) { - String msg = "Issue with bean " + desc + ". Ebean does not support multiple @Id properties. You need to convert to using an @EmbeddedId." - + " Please email the ebean google group if you need further clarification."; - throw new IllegalStateException(msg); - } - if (ids.isEmpty()) { - return null; - } - return ids.get(0); + return id; } public BeanProperty[] getNonTransients() { diff --git a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanTable.java b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanTable.java index d37b4a6e8..0693210dc 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanTable.java +++ b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanTable.java @@ -4,9 +4,6 @@ import io.ebeaninternal.server.deploy.BeanDescriptorMap; import io.ebeaninternal.server.deploy.BeanProperty; import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; -import java.util.List; - - /** * Used for associated beans in place of a BeanDescriptor. This is done to avoid * recursion issues due to the potentially bi-directional and circular @@ -25,12 +22,12 @@ public class DeployBeanTable { */ private String baseTable; - private List idProperties; + private DeployBeanProperty idProperty; /** * Create the BeanTable. */ - public DeployBeanTable(Class beanType) { + DeployBeanTable(Class beanType) { this.beanType = beanType; } @@ -53,12 +50,8 @@ public class DeployBeanTable { /** * Return the id properties. */ - public BeanProperty[] createIdProperties(BeanDescriptorMap owner) { - BeanProperty[] props = new BeanProperty[idProperties.size()]; - for (int i = 0; i < idProperties.size(); i++) { - props[i] = createProperty(owner, idProperties.get(i)); - } - return props; + public BeanProperty createIdProperty(BeanDescriptorMap owner) { + return idProperty == null ? null : createProperty(owner, idProperty); } @SuppressWarnings({"unchecked", "rawtypes"}) @@ -70,14 +63,13 @@ public class DeployBeanTable { } else { return new BeanProperty(prop); } - } /** * Set the Id properties. */ - public void setIdProperties(List idProperties) { - this.idProperties = idProperties; + public void setIdProperty(DeployBeanProperty idProperty) { + this.idProperty = idProperty; } /** diff --git a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployTableJoinColumn.java b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployTableJoinColumn.java index 2df36a716..c45de396f 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployTableJoinColumn.java +++ b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployTableJoinColumn.java @@ -92,9 +92,9 @@ public class DeployTableJoinColumn { private void setReferencedColumn(BeanTable beanTable) { if (localDbColumn == null) { - BeanProperty[] idProperties = beanTable.getIdProperties(); - if (idProperties.length == 1) { - localDbColumn = idProperties[0].getDbColumn(); + BeanProperty idProperty = beanTable.getIdProperty(); + if (idProperty != null) { + localDbColumn = idProperty.getDbColumn(); } } } 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 df4edb1d6..dce5962c1 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java @@ -247,8 +247,8 @@ class AnnotationAssocManys extends AnnotationParser { } if (!intJoin.hasJoinColumns()) { // define foreign key columns - BeanProperty[] localIds = localTable.getIdProperties(); - for (BeanProperty localId : localIds) { + BeanProperty localId = localTable.getIdProperty(); + if (localId != null) { // add the source to intersection join columns String fkCol = localTableName + "_" + localId.getDbColumn(); intJoin.addJoinColumn(new DeployTableJoinColumn(localId.getDbColumn(), namingConvention.getColumnFromProperty(null, fkCol))); @@ -257,8 +257,8 @@ class AnnotationAssocManys extends AnnotationParser { if (!destJoin.hasJoinColumns()) { // define inverse foreign key columns - BeanProperty[] otherIds = otherTable.getIdProperties(); - for (BeanProperty otherId : otherIds) { + BeanProperty otherId = otherTable.getIdProperty(); + if (otherId != null) { // set the intersection to dest table join columns final String fkCol = otherTableName + "_" + otherId.getDbColumn(); destJoin.addJoinColumn(new DeployTableJoinColumn(namingConvention.getColumnFromProperty(null, fkCol), otherId.getDbColumn())); 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 7c614dee6..cd53cd350 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java @@ -92,9 +92,7 @@ public class AnnotationAssocOnes extends AnnotationParser { // May as well check for Id. Makes sense to me. Id id = get(prop, Id.class); if (id != null) { - prop.setEmbedded(); - prop.setId(); - prop.setNullable(false); + readIdAssocOne(prop); } DbForeignKey dbForeignKey = get(prop, DbForeignKey.class); 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 aa4902a9b..c5cb0b810 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java @@ -23,6 +23,7 @@ import javax.persistence.AttributeOverride; import javax.persistence.Column; import javax.persistence.Embeddable; import javax.persistence.Entity; +import javax.persistence.IdClass; import javax.persistence.NamedQuery; import javax.persistence.Table; import javax.persistence.UniqueConstraint; @@ -127,6 +128,11 @@ public class AnnotationClass extends AnnotationParser { } } + IdClass idClass = AnnotationUtil.findAnnotationRecursive(cls, IdClass.class); + if (idClass != null) { + descriptor.setIdClass(idClass.value()); + } + Embeddable embeddable = AnnotationUtil.findAnnotationRecursive(cls, Embeddable.class); if (embeddable != null) { descriptor.setEntityType(EntityType.EMBEDDED); 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 eecdaeda0..a84af1f0c 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java @@ -91,7 +91,7 @@ public class AnnotationFields extends AnnotationParser { */ private FetchType defaultLobFetchType = FetchType.LAZY; - public AnnotationFields(GeneratedPropertyFactory generatedPropFactory, DeployBeanInfo info, + AnnotationFields(GeneratedPropertyFactory generatedPropFactory, DeployBeanInfo info, boolean javaxValidationAnnotations, boolean jacksonAnnotationsPresent, boolean eagerFetchLobs) { super(info, javaxValidationAnnotations); @@ -127,8 +127,7 @@ public class AnnotationFields extends AnnotationParser { Id id = get(prop, Id.class); if (id != null) { - prop.setId(); - prop.setNullable(false); + readIdAssocOne(prop); } EmbeddedId embeddedId = get(prop, EmbeddedId.class); @@ -211,7 +210,7 @@ public class AnnotationFields extends AnnotationParser { Id id = get(prop, Id.class); if (id != null) { - readId(prop); + readIdScalar(prop); } // determine the JDBC type using Lob/Temporal @@ -517,18 +516,6 @@ public class AnnotationFields extends AnnotationParser { return util.createDataEncryptSupport(table, column); } - private void readId(DeployBeanProperty prop) { - - prop.setId(); - prop.setNullable(false); - - if (prop.getPropertyType().equals(UUID.class)) { - if (descriptor.getIdGeneratorName() == null) { - descriptor.setUuidGenerator(); - } - } - } - private void readGenValue(GeneratedValue gen, DeployBeanProperty prop) { String genName = gen.generator(); 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 838f9308d..034caa595 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationParser.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationParser.java @@ -3,6 +3,7 @@ package io.ebeaninternal.server.deploy.parse; import io.ebeaninternal.server.deploy.BeanCascadeInfo; import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; import io.ebeaninternal.server.deploy.meta.DeployBeanProperty; +import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc; import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne; import javax.persistence.AttributeOverride; @@ -11,6 +12,7 @@ import javax.persistence.Column; import javax.validation.groups.Default; import java.util.HashMap; import java.util.Set; +import java.util.UUID; /** * Base class for reading deployment annotations. @@ -39,6 +41,36 @@ public abstract class AnnotationParser extends AnnotationBase { @Override public abstract void parse(); + /** + * Read the Id annotation on an embeddedId. + */ + protected void readIdAssocOne(DeployBeanPropertyAssoc prop) { + prop.setNullable(false); + if (prop.isIdClass()) { + prop.setImportedPrimaryKey(); + } else { + prop.setId(); + prop.setEmbedded(); + } + } + + /** + * Read the Id annotation on scalar property. + */ + protected void readIdScalar(DeployBeanProperty prop) { + prop.setNullable(false); + if (prop.isIdClass()) { + prop.setImportedPrimaryKey(); + } else { + prop.setId(); + if (prop.getPropertyType().equals(UUID.class)) { + if (descriptor.getIdGeneratorName() == null) { + descriptor.setUuidGenerator(); + } + } + } + } + /** * Helper method to set cascade types to the CascadeInfo on BeanProperty. */ 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 41f065a9a..6af425828 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/ReadAnnotations.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/ReadAnnotations.java @@ -83,6 +83,7 @@ public class ReadAnnotations { new AnnotationSql(info, javaxValidationAnnotations).parse(); new AnnotationClass(info).parseAttributeOverride(); + info.getDescriptor().postAnnotations(); } catch (RuntimeException e) { throw new RuntimeException("Error reading annotations for " + info, e); diff --git a/src/main/java/io/ebeaninternal/server/persist/dml/InsertHandler.java b/src/main/java/io/ebeaninternal/server/persist/dml/InsertHandler.java index ec8cdd7df..7887c4b5a 100644 --- a/src/main/java/io/ebeaninternal/server/persist/dml/InsertHandler.java +++ b/src/main/java/io/ebeaninternal/server/persist/dml/InsertHandler.java @@ -69,7 +69,6 @@ public class InsertHandler extends DmlHandler { // expecting a concatenated key that can // be built from supplied AssocOne beans withId = meta.deriveConcatenatedId(persistRequest); - } else if (meta.supportsGetGeneratedKeys()) { // Identity with getGeneratedKeys useGeneratedKeys = true; diff --git a/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindableIdEmbedded.java b/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindableIdEmbedded.java index c415eb943..1f094b0c5 100644 --- a/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindableIdEmbedded.java +++ b/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindableIdEmbedded.java @@ -2,7 +2,6 @@ package io.ebeaninternal.server.persist.dmlbind; import io.ebean.bean.EntityBean; import io.ebeaninternal.server.core.PersistRequestBean; -import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeaninternal.server.deploy.BeanProperty; import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; import io.ebeaninternal.server.persist.dml.GenerateDmlRequest; @@ -15,7 +14,7 @@ import java.util.List; /** * Bindable for a EmbeddedId. */ -public final class BindableIdEmbedded implements BindableId { +final class BindableIdEmbedded implements BindableId { private final BeanPropertyAssocOne embId; @@ -23,10 +22,10 @@ public final class BindableIdEmbedded implements BindableId { private final MatchedImportedProperty[] matches; - public BindableIdEmbedded(BeanPropertyAssocOne embId, BeanDescriptor desc) { + BindableIdEmbedded(BeanPropertyAssocOne embId, MatchedImportedProperty[] matches) { this.embId = embId; this.props = embId.getProperties(); - matches = MatchedImportedProperty.build(props, desc); + this.matches = matches; } @Override @@ -67,13 +66,10 @@ public final class BindableIdEmbedded implements BindableId { public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException { EntityBean idValue = (EntityBean) embId.getValue(bean); - for (BeanProperty prop : props) { - Object value = prop.getValue(idValue); request.bind(value, prop); } - request.setIdValue(idValue); } @@ -88,7 +84,7 @@ public final class BindableIdEmbedded implements BindableId { public boolean deriveConcatenatedId(PersistRequestBean persist) { if (matches == null) { - String m = "Matches for the concatenated key columns where not found?" + String m = "No matches for " + embId.getFullBeanName() + " the concatenated key columns where not found?" + " I expect that the concatenated key was null, and this bean does" + " not have ManyToOne assoc beans matching the primary key columns?"; throw new PersistenceException(m); @@ -100,8 +96,8 @@ public final class BindableIdEmbedded implements BindableId { EntityBean newId = (EntityBean) embId.createEmbeddedId(); // populate it from the assoc one id values... - for (MatchedImportedProperty matche : matches) { - matche.populate(bean, newId); + for (MatchedImportedProperty match : matches) { + match.populate(bean, newId); } // support PropertyChangeSupport 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 48ac8ad1f..36c0998b8 100644 --- a/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryBaseProperties.java +++ b/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryBaseProperties.java @@ -27,9 +27,11 @@ public class FactoryBaseProperties { public void create(List list, BeanDescriptor desc, DmlMode mode, boolean withLobs) { for (BeanProperty prop : desc.propertiesBaseScalar()) { - Bindable item = factoryProperty.create(prop, mode, withLobs); - if (item != null) { - list.add(item); + if (!prop.isImportedPrimaryKey()) { + Bindable item = factoryProperty.create(prop, mode, withLobs); + 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 b568d72d5..585bf1292 100644 --- a/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryEmbedded.java +++ b/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryEmbedded.java @@ -27,18 +27,14 @@ public class FactoryEmbedded { BeanPropertyAssocOne[] embedded = desc.propertiesEmbedded(); for (BeanPropertyAssocOne anEmbedded : embedded) { - BeanProperty[] props = anEmbedded.getProperties(); - List bindList = new ArrayList<>(props.length); - for (BeanProperty prop : props) { Bindable item = factoryProperty.create(prop, mode, withLobs); if (item != null) { bindList.add(item); } } - list.add(new BindableEmbedded(anEmbedded, bindList)); } } diff --git a/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryId.java b/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryId.java index bc67aa4c8..d517e051b 100644 --- a/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryId.java +++ b/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryId.java @@ -20,14 +20,14 @@ public class FactoryId { BeanProperty id = desc.getIdProperty(); if (id == null) { return new BindableIdEmpty(); - } if (!id.isEmbedded()) { return new BindableIdScalar(id); } else { BeanPropertyAssocOne embId = (BeanPropertyAssocOne) id; - return new BindableIdEmbedded(embId, desc); + MatchedImportedProperty[] matches = MatchedImportedFactory.build(embId.getProperties(), desc); + return new BindableIdEmbedded(embId, matches); } } } diff --git a/src/main/java/io/ebeaninternal/server/persist/dmlbind/MatchedImportedEmbedded.java b/src/main/java/io/ebeaninternal/server/persist/dmlbind/MatchedImportedEmbedded.java new file mode 100644 index 000000000..4ce1bb63d --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/persist/dmlbind/MatchedImportedEmbedded.java @@ -0,0 +1,35 @@ +package io.ebeaninternal.server.persist.dmlbind; + +import io.ebean.bean.EntityBean; +import io.ebeaninternal.server.deploy.BeanProperty; +import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; + +/** + * Matches local embedded id properties to 'matching' properties from a + * ManyToOne associated bean that is a 'imported primary key'. + */ +class MatchedImportedEmbedded implements MatchedImportedProperty { + + private final BeanProperty localProp; + + private final BeanPropertyAssocOne assocOne; + + private final BeanProperty foreignProp; + + MatchedImportedEmbedded(BeanProperty localProp, BeanPropertyAssocOne assocOne, BeanProperty foreignProp) { + this.localProp = localProp; + this.assocOne = assocOne; + this.foreignProp = foreignProp; + } + + @Override + public void populate(EntityBean sourceBean, EntityBean embeddedId) { + Object assocBean = assocOne.getValue(sourceBean); + if (assocBean == null) { + throw new NullPointerException("The assoc bean for " + assocOne + " is null?"); + } + Object value = foreignProp.getValue((EntityBean) assocBean); + localProp.setValue(embeddedId, value); + } + +} diff --git a/src/main/java/io/ebeaninternal/server/persist/dmlbind/MatchedImportedFactory.java b/src/main/java/io/ebeaninternal/server/persist/dmlbind/MatchedImportedFactory.java new file mode 100644 index 000000000..f1ba04d9b --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/persist/dmlbind/MatchedImportedFactory.java @@ -0,0 +1,54 @@ +package io.ebeaninternal.server.persist.dmlbind; + +import io.ebeaninternal.server.deploy.BeanDescriptor; +import io.ebeaninternal.server.deploy.BeanProperty; +import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; + +class MatchedImportedFactory { + + /** + * Create the array of matchedImportedProperty based on the properties and descriptor. + */ + protected static MatchedImportedProperty[] build(BeanProperty[] props, BeanDescriptor desc) { + + MatchedImportedProperty[] matches = new MatchedImportedProperty[props.length]; + + for (int i = 0; i < props.length; i++) { + // find matching assoc one property for dbColumn + matches[i] = findMatch(props[i], desc); + if (matches[i] == null) { + // ok, the assoc ones are not on the bean? + return null; + } + } + return matches; + } + + private static MatchedImportedProperty findMatch(BeanProperty prop, BeanDescriptor desc) { + + // find matching against the local database column + String dbColumn = prop.getDbColumn(); + + BeanPropertyAssocOne[] assocOnes = desc.propertiesOne(); + for (BeanPropertyAssocOne assocOne1 : assocOnes) { + if (assocOne1.isImportedPrimaryKey()) { + // search using the ImportedId from the assoc one + BeanProperty foreignMatch = assocOne1.getImportedId().findMatchImport(dbColumn); + if (foreignMatch != null) { + return new MatchedImportedEmbedded(prop, assocOne1, foreignMatch); + } + } + } + + BeanProperty[] scalar = desc.propertiesBaseScalar(); + for (BeanProperty beanProperty : scalar) { + if (dbColumn.equals(beanProperty.getDbColumn())) { + return new MatchedImportedScalar(prop, beanProperty); + } + } + + // there was no matching assoc one property. + // example UserRole bean missing assoc one to User? + return null; + } +} diff --git a/src/main/java/io/ebeaninternal/server/persist/dmlbind/MatchedImportedProperty.java b/src/main/java/io/ebeaninternal/server/persist/dmlbind/MatchedImportedProperty.java index d6b128d6d..cf0a37869 100644 --- a/src/main/java/io/ebeaninternal/server/persist/dmlbind/MatchedImportedProperty.java +++ b/src/main/java/io/ebeaninternal/server/persist/dmlbind/MatchedImportedProperty.java @@ -1,86 +1,11 @@ package io.ebeaninternal.server.persist.dmlbind; import io.ebean.bean.EntityBean; -import io.ebeaninternal.server.deploy.BeanDescriptor; -import io.ebeaninternal.server.deploy.BeanProperty; -import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; -/** - * Matches local embedded id properties to 'matching' properties from a - * ManyToOne associated bean that is a 'imported primary key'. - *

- * This object is designed to help BindableIdEmbedded and BindableIdMap to - * create a concatenated id from the id values from ManyToOne associated beans. - * This can be done when those ManyToOne associated beans make up the primary - * key. This typically means the BindableIdEmbedded is for a intersection table - * of a Many to Many relationship. - *

- */ -class MatchedImportedProperty { - - private final BeanPropertyAssocOne assocOne; - - private final BeanProperty foreignProp; - - private final BeanProperty localProp; - - protected MatchedImportedProperty(BeanPropertyAssocOne assocOne, BeanProperty foreignProp, - BeanProperty localProp) { - this.assocOne = assocOne; - this.foreignProp = foreignProp; - this.localProp = localProp; - } - - protected void populate(EntityBean sourceBean, EntityBean destBean) { - Object assocBean = assocOne.getValue(sourceBean); - if (assocBean == null) { - String msg = "The assoc bean for " + assocOne + " is null?"; - throw new NullPointerException(msg); - } - - Object value = foreignProp.getValue((EntityBean) assocBean); - localProp.setValue(destBean, value); - } +public interface MatchedImportedProperty { /** - * Create the array of matchedImportedProperty based on the properties and descriptor. + * Populate the embeddedId bean from the source entity. */ - protected static MatchedImportedProperty[] build(BeanProperty[] props, BeanDescriptor desc) { - - MatchedImportedProperty[] matches = new MatchedImportedProperty[props.length]; - - for (int i = 0; i < props.length; i++) { - // find matching assoc one property for dbColumn - matches[i] = MatchedImportedProperty.findMatch(props[i], desc); - if (matches[i] == null) { - // ok, the assoc ones are not on the bean? - return null; - } - } - return matches; - } - - private static MatchedImportedProperty findMatch(BeanProperty prop, BeanDescriptor desc) { - - // find matching against the local database column - String dbColumn = prop.getDbColumn(); - - BeanPropertyAssocOne[] assocOnes = desc.propertiesOne(); - for (BeanPropertyAssocOne assocOne1 : assocOnes) { - if (assocOne1.isImportedPrimaryKey()) { - - // search using the ImportedId from the assoc one - BeanProperty foreignMatch = assocOne1.getImportedId().findMatchImport(dbColumn); - - if (foreignMatch != null) { - return new MatchedImportedProperty(assocOne1, foreignMatch, prop); - } - } - } - - // there was no matching assoc one property. - // example UserRole bean missing assoc one to User? - return null; - } - + void populate(EntityBean sourceBean, EntityBean embeddedId); } diff --git a/src/main/java/io/ebeaninternal/server/persist/dmlbind/MatchedImportedScalar.java b/src/main/java/io/ebeaninternal/server/persist/dmlbind/MatchedImportedScalar.java new file mode 100644 index 000000000..436ef69a5 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/persist/dmlbind/MatchedImportedScalar.java @@ -0,0 +1,27 @@ +package io.ebeaninternal.server.persist.dmlbind; + +import io.ebean.bean.EntityBean; +import io.ebeaninternal.server.deploy.BeanProperty; + +/** + * Matches local embedded id properties to 'matching' imported primary key scalar properties. + */ +class MatchedImportedScalar implements MatchedImportedProperty { + + private final BeanProperty localProp; + + private final BeanProperty foreignProp; + + MatchedImportedScalar(BeanProperty localProp, BeanProperty foreignProp) { + this.localProp = localProp; + this.foreignProp = foreignProp; + } + + @Override + public void populate(EntityBean sourceBean, EntityBean embeddedId) { + + Object value = foreignProp.getValue(sourceBean); + localProp.setValue(embeddedId, value); + } + +} diff --git a/src/test/java/org/tests/merge/TestMergeCustomer.java b/src/test/java/org/tests/merge/TestMergeCustomer.java index 8f314cac5..7bf86d1d8 100644 --- a/src/test/java/org/tests/merge/TestMergeCustomer.java +++ b/src/test/java/org/tests/merge/TestMergeCustomer.java @@ -367,7 +367,7 @@ public class TestMergeCustomer extends BaseTestCase { private MContact addContact(String email, String first, String last) { MContact mContact = new MContact(email, first, last); - int i = random.nextInt(2); + int i = 1 + random.nextInt(2); for (int j = 0; j < i; j++) { mContact.getMessages().add(new MContactMessage(first+" "+i, last+" "+i)); } diff --git a/src/test/java/org/tests/model/bridge/BEmbId.java b/src/test/java/org/tests/model/bridge/BEmbId.java new file mode 100644 index 000000000..ad99b4a2d --- /dev/null +++ b/src/test/java/org/tests/model/bridge/BEmbId.java @@ -0,0 +1,53 @@ +package org.tests.model.bridge; + +import javax.persistence.Embeddable; +import java.util.Objects; +import java.util.UUID; + +@Embeddable +public class BEmbId { + + private UUID siteId; + private UUID userId; + + public BEmbId(UUID siteId, UUID userId) { + this.siteId = siteId; + this.userId = userId; + } + + public UUID getSiteId() { + return siteId; + } + + public UUID getUserId() { + return userId; + } + + @Override + public String toString() { + return "st:" + siteId + " ui:" + userId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + BEmbId that = (BEmbId) o; + return Objects.equals(siteId, that.siteId) && Objects.equals(userId, that.userId); + } + + @Override + public int hashCode() { + return Objects.hash(siteId, userId); + } + + /** + * Just simulating the hash from Objects.hash(...) + */ + int otherHash() { + int result = 31 + siteId.hashCode(); + result = 31 * result + userId.hashCode(); + return result; + } +} diff --git a/src/test/java/org/tests/model/bridge/BSiteUser.java b/src/test/java/org/tests/model/bridge/BSiteUserA.java similarity index 67% rename from src/test/java/org/tests/model/bridge/BSiteUser.java rename to src/test/java/org/tests/model/bridge/BSiteUserA.java index b4ff1724e..e3c57c10d 100644 --- a/src/test/java/org/tests/model/bridge/BSiteUser.java +++ b/src/test/java/org/tests/model/bridge/BSiteUserA.java @@ -4,10 +4,12 @@ import javax.persistence.Embeddable; import javax.persistence.EmbeddedId; import javax.persistence.Entity; import javax.persistence.ManyToOne; +import javax.persistence.Version; +import java.util.Objects; import java.util.UUID; @Entity -public class BSiteUser { +public class BSiteUserA { @Embeddable public static class Id { @@ -16,21 +18,23 @@ public class BSiteUser { public UUID siteId; public UUID userId; + public Id(UUID siteId, UUID userId) { + this.siteId = siteId; + this.userId = userId; + } + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - Id id = (Id) o; - if (!siteId.equals(id.siteId)) return false; - return userId.equals(id.userId); + Id that = (Id) o; + return Objects.equals(siteId, that.siteId) && Objects.equals(userId, that.userId); } @Override public int hashCode() { - int result = siteId.hashCode(); - result = 31 * result + userId.hashCode(); - return result; + return Objects.hash(siteId, userId); } } @@ -45,8 +49,10 @@ public class BSiteUser { @ManyToOne(optional = false) private final BUser user; + @Version + private long version; - public BSiteUser(BAccessLevel accessLevel, BSite site, BUser user) { + public BSiteUserA(BAccessLevel accessLevel, BSite site, BUser user) { this.accessLevel = accessLevel; this.site = site; this.user = user; @@ -75,4 +81,12 @@ public class BSiteUser { public void setAccessLevel(BAccessLevel accessLevel) { this.accessLevel = accessLevel; } + + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } } diff --git a/src/test/java/org/tests/model/bridge/BSiteUserD.java b/src/test/java/org/tests/model/bridge/BSiteUserD.java new file mode 100644 index 000000000..4ff4513f4 --- /dev/null +++ b/src/test/java/org/tests/model/bridge/BSiteUserD.java @@ -0,0 +1,61 @@ +package org.tests.model.bridge; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.Version; +import java.util.UUID; + +@Entity +@IdClass(BEmbId.class) +public class BSiteUserD { + + @Id + private UUID siteId; + + @Id + private UUID userId; + + private BAccessLevel accessLevel; + + @Version + private long version; + + public BSiteUserD(BAccessLevel accessLevel, UUID siteId, UUID userId) { + this.accessLevel = accessLevel; + this.siteId = siteId; + this.userId = userId; + } + + public UUID getSiteId() { + return siteId; + } + + public void setSiteId(UUID siteId) { + this.siteId = siteId; + } + + public UUID getUserId() { + return userId; + } + + public void setUserId(UUID userId) { + this.userId = userId; + } + + public BAccessLevel getAccessLevel() { + return accessLevel; + } + + public void setAccessLevel(BAccessLevel accessLevel) { + this.accessLevel = accessLevel; + } + + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } +} diff --git a/src/test/java/org/tests/model/bridge/BSiteUserE.java b/src/test/java/org/tests/model/bridge/BSiteUserE.java new file mode 100644 index 000000000..a6795de77 --- /dev/null +++ b/src/test/java/org/tests/model/bridge/BSiteUserE.java @@ -0,0 +1,44 @@ +package org.tests.model.bridge; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.ManyToOne; + +@Entity +@IdClass(BEmbId.class) +public class BSiteUserE { + + @Id + @ManyToOne + private final BSite site; + + @Id + @ManyToOne + private final BUser user; + + private BAccessLevel accessLevel; + + + public BSiteUserE(BAccessLevel accessLevel, BSite site, BUser user) { + this.accessLevel = accessLevel; + this.site = site; + this.user = user; + } + + public BAccessLevel getAccessLevel() { + return accessLevel; + } + + public BSite getSite() { + return site; + } + + public BUser getUser() { + return user; + } + + public void setAccessLevel(BAccessLevel accessLevel) { + this.accessLevel = accessLevel; + } +} diff --git a/src/test/java/org/tests/model/bridge/TestExplicitM2MBridgeTable.java b/src/test/java/org/tests/model/bridge/TestExplicitM2MBridgeTable.java index 142f9aee5..ed22ae4c4 100644 --- a/src/test/java/org/tests/model/bridge/TestExplicitM2MBridgeTable.java +++ b/src/test/java/org/tests/model/bridge/TestExplicitM2MBridgeTable.java @@ -19,7 +19,7 @@ public class TestExplicitM2MBridgeTable extends BaseTestCase { Ebean.save(user); Ebean.save(site); - insertUpdateBridge(user, site); + insertUpdateBridgeA(user, site); insertUpdateBridgeB(user, site); insertUpdateBridgeC(user, site); } @@ -27,21 +27,26 @@ public class TestExplicitM2MBridgeTable extends BaseTestCase { /** * Test where matching by db column naming convention. */ - private void insertUpdateBridge(BUser user, BSite site) { + private void insertUpdateBridgeA(BUser user, BSite site) { - BSiteUser access = new BSiteUser(BAccessLevel.ONE, site, user); + BSiteUserA access = new BSiteUserA(BAccessLevel.ONE, site, user); Ebean.save(access); access.setAccessLevel(BAccessLevel.TWO); Ebean.save(access); - List list = Ebean.find(BSiteUser.class).findList(); + List list = Ebean.find(BSiteUserA.class).findList(); assertThat(list).isNotEmpty(); - for (BSiteUser bridge : list) { + for (BSiteUserA bridge : list) { assertThat(bridge.getId().siteId).isEqualTo(bridge.getSite().id); assertThat(bridge.getId().userId).isEqualTo(bridge.getUser().id); } + + BSiteUserA found = Ebean.find(BSiteUserA.class, new BSiteUserA.Id(site.id, user.id)); + found.setAccessLevel(BAccessLevel.THREE); + + Ebean.save(found); } /** diff --git a/src/test/java/org/tests/model/bridge/TestIdClassScalar.java b/src/test/java/org/tests/model/bridge/TestIdClassScalar.java new file mode 100644 index 000000000..5ad291d0d --- /dev/null +++ b/src/test/java/org/tests/model/bridge/TestIdClassScalar.java @@ -0,0 +1,143 @@ +package org.tests.model.bridge; + +import io.ebean.BaseTestCase; +import io.ebean.Ebean; +import org.ebeantest.LoggedSqlCollector; +import org.junit.Test; + +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestIdClassScalar extends BaseTestCase { + + private BUser user = new BUser("Fiona"); + private BSite site = new BSite("avaje.io"); + + @Test + public void testBEmbId_equalsHashcode() { + + BEmbId a = new BEmbId(UUID.randomUUID(), UUID.randomUUID()); + BEmbId b = new BEmbId(a.getSiteId(), a.getUserId()); + + BEmbId c = new BEmbId(UUID.randomUUID(), UUID.randomUUID()); + + BEmbId d = new BEmbId(a.getSiteId(), UUID.randomUUID()); + + assertThat(a).isEqualTo(b); + assertThat(a.hashCode()).isEqualTo(b.hashCode()); + + assertThat(a).isNotEqualTo(c); + assertThat(a.hashCode()).isNotEqualTo(c.hashCode()); + + assertThat(a).isNotEqualTo(d); + assertThat(a.hashCode()).isNotEqualTo(d.hashCode()); + + + assertThat(a.hashCode()).isEqualTo(a.otherHash()); + + } + + @Test + public void test() { + + Ebean.save(user); + Ebean.save(site); + + insertUpdateBridgeD(user, site); + insertUpdateBridgeE(user, site); + } + + /** + * Test where matching by db column naming convention. + */ + private void insertUpdateBridgeD(BUser user, BSite site) { + + LoggedSqlCollector.start(); + + BSiteUserD access = new BSiteUserD(BAccessLevel.ONE, site.id, user.id); + Ebean.save(access); + + access.setAccessLevel(BAccessLevel.TWO); + Ebean.save(access); + + List list = Ebean.find(BSiteUserD.class).findList(); + assertThat(list).isNotEmpty(); + + for (BSiteUserD bridge : list) { + assertThat(bridge.getSiteId()).isEqualTo(site.id); + assertThat(bridge.getUserId()).isEqualTo(user.id); + } + + List sql = LoggedSqlCollector.current(); + assertThat(sql).hasSize(3); + assertThat(sql.get(0)).contains("insert into bsite_user_d (site_id, user_id, access_level, version) values (?,?,?,?)"); + assertThat(sql.get(1)).contains("update bsite_user_d set access_level=?, version=? where site_id=? and user_id=? and version=?"); + assertThat(trimSql(sql.get(2))).contains("select t0.site_id, t0.user_id, t0.site_id, t0.user_id, t0.access_level, t0.version from bsite_user_d t0"); + + + BEmbId id = new BEmbId(site.id, user.id); + BSiteUserD one = Ebean.find(BSiteUserD.class, id); + + assertThat(one).isNotNull(); + assertThat(one.getSiteId()).isEqualTo(site.id); + assertThat(one.getUserId()).isEqualTo(user.id); + + one.setAccessLevel(BAccessLevel.THREE); + Ebean.save(one); + + sql = LoggedSqlCollector.stop(); + + assertThat(sql).hasSize(2); + assertThat(trimSql(sql.get(0))).contains("select t0.site_id, t0.user_id, t0.site_id, t0.user_id, t0.access_level, t0.version from bsite_user_d t0 where t0.site_id = ? and t0.user_id = ?"); + assertThat(sql.get(1)).contains("update bsite_user_d set access_level=?, version=? where site_id=? and user_id=? and version=?"); + } + + /** + * Test where matching by db column naming convention. + */ + private void insertUpdateBridgeE(BUser user, BSite site) { + + LoggedSqlCollector.start(); + + BSiteUserE access = new BSiteUserE(BAccessLevel.ONE, site, user); + Ebean.save(access); + + access.setAccessLevel(BAccessLevel.TWO); + Ebean.save(access); + + List list = Ebean.find(BSiteUserE.class).findList(); + assertThat(list).isNotEmpty(); + + List sql = LoggedSqlCollector.current(); + assertThat(sql).hasSize(3); + assertThat(sql.get(0)).contains("insert into bsite_user_e (site_id, user_id, access_level)"); + assertThat(sql.get(1)).contains("update bsite_user_e set access_level=? where site_id=? and user_id=?"); + assertThat(trimSql(sql.get(2))).contains("select t0.site_id, t0.user_id, t0.access_level, t0.site_id, t0.user_id from bsite_user_e t0"); + + + for (BSiteUserE bridge : list) { + assertThat(bridge.getSite().id).isEqualTo(site.id); + assertThat(bridge.getUser().id).isEqualTo(user.id); + } + + BEmbId id = new BEmbId(site.id, user.id); + + BSiteUserE one = Ebean.find(BSiteUserE.class, id); + + assertThat(one).isNotNull(); + assertThat(one.getSite().id).isEqualTo(site.id); + assertThat(one.getUser().id).isEqualTo(user.id); + + one.setAccessLevel(BAccessLevel.THREE); + Ebean.save(one); + + sql = LoggedSqlCollector.stop(); + + assertThat(sql).hasSize(2); + assertThat(trimSql(sql.get(0))).contains("select t0.site_id, t0.user_id, t0.access_level, t0.site_id, t0.user_id from bsite_user_e t0 where t0.site_id = ? and t0.user_id = ?"); + assertThat(sql.get(1)).contains("update bsite_user_e set access_level=? where site_id=? and user_id=?"); + + } +}