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=?");
+
+ }
+}