diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index 73bb1c126..4228a3044 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -212,7 +212,6 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { private final EntityBean prototypeEntityBean; private final IdBinder idBinder; - private final String idSelect; private String idBinderInLHSSql; private String idBinderIdSql; private String deleteByIdSql; @@ -355,23 +354,11 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { propertiesIndex[i] = propMap.get(ebi.property(i)); } } - idSelect = initIdSelect(); } - String initIdSelect() { - if (idProperty != null && !idProperty.name().equals("_idClass")) { - return idProperty.name(); - } else if (entityType == EntityType.EMBEDDED) { - return null; - } else { - StringJoiner sj = new StringJoiner(","); - for (BeanProperty prop : propertiesNonMany) { - if (prop.isImportedPrimaryKey()) { - sj.add(prop.name()); - } - } - return sj.toString().intern(); - } + public String idSelect() { + if (idBinder == null) throw new UnsupportedOperationException(); + return idBinder.idSelect(); } public boolean isJacksonCorePresent() { @@ -2450,9 +2437,13 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { BeanProperty _findBeanProperty(String propName) { BeanProperty prop = propMap.get(propName); - if (prop == null && inheritInfo != null) { - // search in sub types... - return inheritInfo.findSubTypeProperty(propName); + if (prop == null) { + if ("_idClass".equals(propName)) { + return idProperty; + } else if (inheritInfo != null) { + // search in sub types... + return inheritInfo.findSubTypeProperty(propName); + } } return prop; } @@ -3057,10 +3048,6 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType { return idProperty; } - public String idSelect() { - return idSelect; - } - /** * Return true if this bean should be inserted rather than updated. * diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorElement.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorElement.java index 938a4c3d4..c69fbb265 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorElement.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorElement.java @@ -23,11 +23,6 @@ abstract class BeanDescriptorElement extends BeanDescriptor { this.elementHelp = elementHelp; } - @Override - String initIdSelect() { - return null; - } - private String shortName(String name) { int pos = name.lastIndexOf('.'); if (pos > 1) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/IdBinder.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/IdBinder.java index 89a3677a2..8c444fa4a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/IdBinder.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/IdBinder.java @@ -26,6 +26,8 @@ public interface IdBinder { */ void initialise(); + String idSelect(); + /** * Return true if this is a compound key and must use expanded and or form. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/IdBinderEmbedded.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/IdBinderEmbedded.java index d2fda9bd2..449216177 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/IdBinderEmbedded.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/IdBinderEmbedded.java @@ -45,6 +45,11 @@ public final class IdBinderEmbedded implements IdBinder { this.idInValueSql = idInExpandedForm ? idInExpanded() : idInCompressed(); } + @Override + public String idSelect() { + return embIdProperty.name(); + } + @Override public boolean isIdInExpandedForm() { return idInExpandedForm; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/IdBinderEmpty.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/IdBinderEmpty.java index b4bbbff1a..251086384 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/IdBinderEmpty.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/IdBinderEmpty.java @@ -2,11 +2,11 @@ package io.ebeaninternal.server.deploy.id; import io.ebean.bean.EntityBean; import io.ebeaninternal.api.SpiExpressionRequest; +import io.ebeaninternal.server.bind.DataBind; import io.ebeaninternal.server.core.DefaultSqlUpdate; import io.ebeaninternal.server.deploy.BeanProperty; import io.ebeaninternal.server.deploy.DbReadContext; import io.ebeaninternal.server.deploy.DbSqlContext; -import io.ebeaninternal.server.bind.DataBind; import java.io.DataInput; import java.io.DataOutput; @@ -27,6 +27,11 @@ final class IdBinderEmpty implements IdBinder { public void initialise() { } + @Override + public String idSelect() { + return ""; + } + @Override public boolean isIdInExpandedForm() { return false; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/IdBinderSimple.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/IdBinderSimple.java index 729ad0adb..511a76c79 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/IdBinderSimple.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/id/IdBinderSimple.java @@ -45,6 +45,11 @@ public final class IdBinderSimple implements IdBinder { // do nothing } + @Override + public String idSelect() { + return idProperty.name(); + } + @Override public boolean isIdInExpandedForm() { return false; diff --git a/ebean-test/src/test/java/org/tests/cache/embeddedid/Concept.java b/ebean-test/src/test/java/org/tests/cache/embeddedid/Concept.java new file mode 100644 index 000000000..a61aeadf5 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/cache/embeddedid/Concept.java @@ -0,0 +1,53 @@ +package org.tests.cache.embeddedid; + +import javax.persistence.*; +import java.util.List; + +@Entity +@IdClass(ConceptId.class) +@SuppressWarnings("unused") +public class Concept { + @Id + private String id; + + @Id + private String networkId; + + private String label; + + @OneToMany(mappedBy = "from", cascade = {CascadeType.ALL}) + private List outgoingConnections; + + @OneToMany(mappedBy = "to", cascade = {CascadeType.ALL}) + private List incomingConnections; + + public Concept(String networkId, String id, String label) { + this.networkId = networkId; + this.id = id; + this.label = label; + } + + public String id() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String networkId() { + return networkId; + } + + public void setNetworkId(String networkId) { + this.networkId = networkId; + } + + public String label() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } +} diff --git a/ebean-test/src/test/java/org/tests/cache/embeddedid/ConceptId.java b/ebean-test/src/test/java/org/tests/cache/embeddedid/ConceptId.java new file mode 100644 index 000000000..4db440388 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/cache/embeddedid/ConceptId.java @@ -0,0 +1,31 @@ +package org.tests.cache.embeddedid; + +import javax.persistence.Embeddable; + +@Embeddable +public class ConceptId { + private final String networkId; + private final String id; + + public ConceptId(String networkId, String id) { + this.networkId = networkId; + this.id = id; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ConceptId conceptId = (ConceptId) o; + if (!id.equals(conceptId.id)) return false; + return networkId.equals(conceptId.networkId); + } + + @Override + public int hashCode() { + int result = id.hashCode(); + result = 31 * result + networkId.hashCode(); + return result; + } +} diff --git a/ebean-test/src/test/java/org/tests/cache/embeddedid/Connection.java b/ebean-test/src/test/java/org/tests/cache/embeddedid/Connection.java new file mode 100644 index 000000000..2716ec70f --- /dev/null +++ b/ebean-test/src/test/java/org/tests/cache/embeddedid/Connection.java @@ -0,0 +1,87 @@ +package org.tests.cache.embeddedid; + +import javax.persistence.*; + +@Entity +@IdClass(ConceptId.class) +@SuppressWarnings("unused") +public class Connection { + @Id + private String id; + + @Id + private String networkId; + + private String label; + + @ManyToOne(optional = false) + @JoinColumns({ + @JoinColumn(name = "from_conc", referencedColumnName = "id", nullable = false), + @JoinColumn( + name = "network_id", referencedColumnName = "network_id", + nullable = false, insertable = false, updatable = false + ) + }) + private Concept from; + + @ManyToOne(optional = false) + @JoinColumns({ + @JoinColumn(name = "to_conc", referencedColumnName = "id", nullable = false), + @JoinColumn( + name = "network_id", referencedColumnName = "network_id", + nullable = false, insertable = false, updatable = false + ) + }) + private Concept to; + + public Connection( + String networkId, String id, String label, + Concept from, Concept to + ) { + this.networkId = networkId; + this.id = id; + this.label = label; + this.from = from; + this.to = to; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getNetworkId() { + return networkId; + } + + public void setNetworkId(String networkId) { + this.networkId = networkId; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public Concept from() { + return from; + } + + public void setFrom(Concept from) { + this.from = from; + } + + public Concept to() { + return to; + } + + public void setTo(Concept to) { + this.to = to; + } +} diff --git a/ebean-test/src/test/java/org/tests/cache/embeddedid/TestCompositeForeignKey.java b/ebean-test/src/test/java/org/tests/cache/embeddedid/TestCompositeForeignKey.java new file mode 100644 index 000000000..0fdf06910 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/cache/embeddedid/TestCompositeForeignKey.java @@ -0,0 +1,50 @@ +package org.tests.cache.embeddedid; + +import io.ebean.DB; +import io.ebean.test.LoggedSql; +import io.ebean.xtest.BaseTestCase; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Objects; + +import static org.assertj.core.api.Assertions.assertThat; + +class TestCompositeForeignKey extends BaseTestCase { + + @Test + void createConnectionWithCompositeForeignKey() { + String networkId = "test-network"; + Concept concept1 = new Concept(networkId, "concept1", "concept 1"); + Concept concept2 = new Concept(networkId, "concept2", "concept 2"); + + DB.saveAll(concept1, concept2); + + String connectionId = "test-connection"; + + Connection connection = new Connection( + networkId, connectionId, "test", concept1, concept2 + ); + DB.save(connection); + + Connection reloaded = DB.find(Connection.class, new ConceptId(networkId, connectionId)); + Objects.requireNonNull(reloaded); + + LoggedSql.start(); + // this breaks if we uncomment the OneToMany relationships with cascade = ALL + DB.createQuery(Concept.class).delete(); + + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(4); + assertThat(sql.get(0)).contains("select t0.network_id, t0.id from concept t0"); + if (isSqlServer() || isDb2() || isNuoDb()) { + assertThat(sql.get(1)).contains("delete from connection where "); + assertThat(sql.get(2)).contains("delete from connection where "); + assertThat(sql.get(3)).contains("delete from concept where "); + } else { + assertThat(sql.get(1)).contains("delete from connection where (network_id,from_conc) in"); + assertThat(sql.get(2)).contains("delete from connection where (network_id,to_conc) in"); + assertThat(sql.get(3)).contains("delete from concept where (network_id,id) in"); + } + } +}