diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java index c3aeb15c07..ef10e0dbc6 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java @@ -436,14 +436,15 @@ ImportedId createImportedId(BeanPropertyAssoc owner, BeanDescriptor target } TableJoinColumn[] cols = join.columns(); if (!idProp.isEmbedded()) { - // simple single scalar id - if (cols.length != 1) { - CoreLog.log.log(ERROR, "No Imported Id column for {0} in table {1}", idProp, join.getTable()); - return null; - } else { - BeanProperty[] idProps = {idProp}; - return createImportedScalar(owner, cols[0], idProps, others); + // simple single scalar id, match on the foreign column, allow extra TableJoinColumn for #3664 + String matchColumn = idProp.dbColumn(); + for (TableJoinColumn col : cols) { + if (matchColumn.equals(col.getForeignDbColumn())) { + return createImportedScalar(owner, col, new BeanProperty[]{idProp}, others); + } } + CoreLog.log.log(ERROR, "No Imported Id column for {0} in table {1}", idProp, join.getTable()); + return null; } else { // embedded id BeanPropertyAssocOne embProp = (BeanPropertyAssocOne) idProp; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java index a0b8554e70..5bb4fe2c02 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocOne.java @@ -128,16 +128,6 @@ private void initialiseAssocOne(String embeddedPrefix) { throw new PersistenceException("Cannot find imported id for " + fullName() + " from " + targetDescriptor + ". If using native-image, possibly missing reflect-config for the Id property."); } - if (importedId.isScalar()) { - // limit JoinColumn mapping to the @Id / primary key - TableJoinColumn[] columns = tableJoin.columns(); - String foreignJoinColumn = columns[0].getForeignDbColumn(); - String foreignIdColumn = targetDescriptor.idProperty().dbColumn(); - if (!foreignJoinColumn.equalsIgnoreCase(foreignIdColumn)) { - throw new PersistenceException("Mapping limitation - @JoinColumn on " + fullName() + " needs to map to a primary key as per Issue #529 " - + " - joining to " + foreignJoinColumn + " and not " + foreignIdColumn); - } - } } else { exportedProperties = createExported(); String delStmt = "delete from " + targetDescriptor.baseTable() + " where "; diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java index d11b679b4c..ca69e2e134 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java @@ -195,7 +195,7 @@ public void visitOneImported(BeanPropertyAssocOne p) { String dbCol = column.getLocalDbColumn(); BeanProperty importedProperty = p.findMatchImport(dbCol); if (importedProperty == null) { - throw new RuntimeException("Imported BeanProperty not found?"); + continue; } String columnDefn = ctx.getColumnDefn(importedProperty, true); String refColumn = importedProperty.dbColumn(); diff --git a/ebean-test/src/test/java/org/tests/model/m2o/MTJOrder.java b/ebean-test/src/test/java/org/tests/model/m2o/MTJOrder.java new file mode 100644 index 0000000000..160e252256 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/model/m2o/MTJOrder.java @@ -0,0 +1,45 @@ +package org.tests.model.m2o; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +@Entity +public class MTJOrder { + + @Id + private long id; + + @Column(name = "org_id") + private long orgId; + + @Column + private String other; + + public Long id() { + return id; + } + + public MTJOrder setId(Long id) { + this.id = id; + return this; + } + + public Long orgId() { + return orgId; + } + + public MTJOrder setOrgId(Long orgId) { + this.orgId = orgId; + return this; + } + + public String other() { + return other; + } + + public MTJOrder setOther(String other) { + this.other = other; + return this; + } +} diff --git a/ebean-test/src/test/java/org/tests/model/m2o/MTJTrans.java b/ebean-test/src/test/java/org/tests/model/m2o/MTJTrans.java new file mode 100644 index 0000000000..589f004e97 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/model/m2o/MTJTrans.java @@ -0,0 +1,47 @@ +package org.tests.model.m2o; + +import jakarta.persistence.*; + +@Entity +public class MTJTrans { + + @Id + private long id; + + @Column(name = "org_id") + private long orgId; + + @ManyToOne + @JoinColumns({ + @JoinColumn(name = "org_id", referencedColumnName = "org_id"), // extra join column, not strictly needed + @JoinColumn(name = "order_id", referencedColumnName = "id") + }) + private MTJOrder order; + + public Long id() { + return id; + } + + public MTJTrans setId(Long id) { + this.id = id; + return this; + } + + public Long orgId() { + return orgId; + } + + public MTJTrans setOrgId(Long orgId) { + this.orgId = orgId; + return this; + } + + public MTJOrder order() { + return order; + } + + public MTJTrans setOrder(MTJOrder order) { + this.order = order; + return this; + } +} diff --git a/ebean-test/src/test/java/org/tests/model/m2o/TestMTJoinColumns.java b/ebean-test/src/test/java/org/tests/model/m2o/TestMTJoinColumns.java new file mode 100644 index 0000000000..c7215ca307 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/model/m2o/TestMTJoinColumns.java @@ -0,0 +1,48 @@ +package org.tests.model.m2o; + +import io.ebean.DB; +import io.ebean.test.LoggedSql; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class TestMTJoinColumns { + + @Test + void test() { + MTJTrans parent = new MTJTrans(); + parent.setOrgId(51L); + + DB.save(parent); + + MTJTrans found = DB.find(MTJTrans.class) + .setId(parent.id()) + .fetch("order") + .findOne(); + + assertThat(found.order()).isNull(); + + var order = new MTJOrder() + .setOrgId(51L) + .setOther("some"); + DB.save(order); + found.setOrder(order); + DB.save(found); + + LoggedSql.start(); + MTJTrans found2 = DB.find(MTJTrans.class) + .setId(parent.id()) + .fetch("order") + .findOne(); + + assertThat(found2.order()).isNotNull(); + assertThat(found2.order().id()).isEqualTo(order.id()); + assertThat(found2.order().other()).isEqualTo("some"); + + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(1); + assertThat(sql.get(0)).contains("from mtjtrans t0 left join mtjorder t1 on t1.org_id = t0.org_id and t1.id = t0.order_id where t0.id = ?"); + } +}