From 31cac603938a9c2553ee6989c0fca3be2accd2fb Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Thu, 15 Feb 2018 00:49:51 +1300 Subject: [PATCH] #107 - Mapping - Add support for @PrimaryKeyJoinColumn/@PrimaryKeyJoinColumns Adds Bi-directional support --- .../server/deploy/BeanDescriptorManager.java | 30 ++++++- .../deploy/meta/DeployBeanProperty.java | 6 +- .../deploy/meta/DeployBeanPropertyAssoc.java | 2 +- .../meta/DeployBeanPropertyAssocOne.java | 27 +++++- .../deploy/parse/AnnotationAssocOnes.java | 12 +-- .../org/tests/model/onetoone/OtoUBPrime.java | 65 ++++++++++++++ .../tests/model/onetoone/OtoUBPrimeExtra.java | 67 ++++++++++++++ .../TestOneToOnePrimaryKeyJoinBidi.java | 87 +++++++++++++++++++ 8 files changed, 278 insertions(+), 18 deletions(-) create mode 100644 src/test/java/org/tests/model/onetoone/OtoUBPrime.java create mode 100644 src/test/java/org/tests/model/onetoone/OtoUBPrimeExtra.java create mode 100644 src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoinBidi.java diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java index c9341c74a..d9452bc8f 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java @@ -797,8 +797,12 @@ public class BeanDescriptorManager implements BeanDescriptorMap { // We only perform 'circular' checks etc after we have // all the DeployBeanDescriptors created and in the map. + List> primaryKeyJoinCheck = new ArrayList<>(); for (DeployBeanInfo info : deployInfoMap.values()) { - checkMappedBy(info); + checkMappedBy(info, primaryKeyJoinCheck); + } + for (DeployBeanPropertyAssocOne prop : primaryKeyJoinCheck) { + checkUniDirectionalPrimaryKeyJoin(prop); } for (DeployBeanInfo info : deployInfoMap.values()) { @@ -868,12 +872,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap { * relationships. *

*/ - private void checkMappedBy(DeployBeanInfo info) { + private void checkMappedBy(DeployBeanInfo info, List> primaryKeyJoinCheck) { for (DeployBeanPropertyAssocOne oneProp : info.getDescriptor().propertiesAssocOne()) { if (!oneProp.isTransient()) { if (oneProp.getMappedBy() != null) { checkMappedByOneToOne(oneProp); + } else if (oneProp.isPrimaryKeyJoin()) { + primaryKeyJoinCheck.add(oneProp); } } } @@ -1071,6 +1077,21 @@ public class BeanDescriptorManager implements BeanDescriptorMap { DeployTableJoin otherTableJoin = mappedAssocOne.getTableJoin(); otherTableJoin.copyWithoutType(tableJoin, true, tableJoin.getTable()); } + + if (mappedAssocOne.isPrimaryKeyJoin()) { + // bi-directional PrimaryKeyJoin ... + mappedAssocOne.setPrimaryKeyJoin(false); + prop.setPrimaryKeyExport(); + addPrimaryKeyJoin(prop); + } + } + + private void checkUniDirectionalPrimaryKeyJoin(DeployBeanPropertyAssocOne prop) { + if (prop.isPrimaryKeyJoin()) { + // uni-directional PrimaryKeyJoin ... + prop.setPrimaryKeyExport(); + addPrimaryKeyJoin(prop); + } } /** @@ -1546,7 +1567,10 @@ public class BeanDescriptorManager implements BeanDescriptorMap { return changeLogListener; } - public void addPrimaryKeyJoin(DeployBeanPropertyAssocOne prop, DeployTableJoin inverse) { + public void addPrimaryKeyJoin(DeployBeanPropertyAssocOne prop) { + + String baseTable = prop.getDesc().getBaseTable(); + DeployTableJoin inverse = prop.getTableJoin().createInverse(baseTable); TableJoin inverseJoin = new TableJoin(inverse); 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 c588028f5..5e2cfa517 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java +++ b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java @@ -17,8 +17,8 @@ import io.ebean.config.dbplatform.DbEncryptFunction; import io.ebean.util.AnnotationUtil; import io.ebeaninternal.server.core.InternString; import io.ebeaninternal.server.deploy.BeanProperty; -import io.ebeaninternal.server.deploy.DeployDocPropertyOptions; import io.ebeaninternal.server.deploy.DbMigrationInfo; +import io.ebeaninternal.server.deploy.DeployDocPropertyOptions; import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty; import io.ebeaninternal.server.el.ElPropertyValue; import io.ebeaninternal.server.properties.BeanPropertyGetter; @@ -284,6 +284,10 @@ public class DeployBeanProperty { return desc.getFullName() + "." + name; } + public DeployBeanDescriptor getDesc() { + return desc; + } + /** * Return the DB column length for character columns. *

diff --git a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java index e0bbc9f0e..cbe3dc48f 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java +++ b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssoc.java @@ -16,7 +16,7 @@ public abstract class DeployBeanPropertyAssoc extends DeployBeanProperty { /** * Persist settings. */ - private final BeanCascadeInfo cascadeInfo = new BeanCascadeInfo(); + protected final BeanCascadeInfo cascadeInfo = new BeanCascadeInfo(); /** * The join table information. 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 dc21bbe82..9e257def2 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocOne.java +++ b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocOne.java @@ -1,5 +1,7 @@ package io.ebeaninternal.server.deploy.meta; +import javax.persistence.CascadeType; + /** * Property mapped to a joined bean. */ @@ -9,6 +11,8 @@ public class DeployBeanPropertyAssocOne extends DeployBeanPropertyAssoc { private boolean oneToOneExported; + private boolean primaryKeyJoin; + private boolean primaryKeyExport; private boolean importedPrimaryKey; @@ -118,11 +122,30 @@ public class DeployBeanPropertyAssocOne extends DeployBeanPropertyAssoc { return columnPrefix; } - public void setPrimaryKeyExport() { - this.primaryKeyExport = true; + /** + * Mark as PrimaryKeyJoin (we don't know which side is the export side initially). + */ + public void setPrimaryKeyJoin(boolean primaryKeyJoin) { + this.primaryKeyJoin = primaryKeyJoin; + } + + public boolean isPrimaryKeyJoin() { + return primaryKeyJoin; } public boolean isPrimaryKeyExport() { return primaryKeyExport; } + + /** + * Set as export side of OneToOne with PrimaryKeyJoin. + */ + public void setPrimaryKeyExport() { + this.primaryKeyExport = true; + this.oneToOneExported = true; + if (!cascadeInfo.isSave()) { + // we pretty much need to cascade save so turning that on automatically ... + cascadeInfo.setType(CascadeType.ALL); + } + } } 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 985608df7..4e5b04e36 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java @@ -8,13 +8,11 @@ import io.ebeaninternal.server.deploy.BeanTable; import io.ebeaninternal.server.deploy.meta.DeployBeanProperty; import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc; import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne; -import io.ebeaninternal.server.deploy.meta.DeployTableJoin; import io.ebeaninternal.server.deploy.meta.DeployTableJoinColumn; import io.ebeaninternal.server.query.SqlJoinType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Embedded; import javax.persistence.EmbeddedId; @@ -212,12 +210,7 @@ public class AnnotationAssocOnes extends AnnotationParser { if (!prop.isOneToOne()) { throw new IllegalStateException("Expecting property " + prop.getFullBeanName() + " with PrimaryKeyJoinColumn to be a OneToOne?"); } - prop.setPrimaryKeyExport(); - prop.setOneToOneExported(); - if (!prop.getCascadeInfo().isSave()) { - // we pretty much need to cascade save so turning that on automatically ... - prop.getCascadeInfo().setType(CascadeType.ALL); - } + prop.setPrimaryKeyJoin(true); if (!primaryKeyJoin.name().isEmpty()) { log.warn("Automatically determining join columns and ignoring PrimaryKeyJoinColumn.name {} on {}", primaryKeyJoin.name(), prop.getFullBeanName()); @@ -232,9 +225,6 @@ public class AnnotationAssocOnes extends AnnotationParser { String foreignColumn = beanTable(prop).getIdColumn(); prop.getTableJoin().addJoinColumn(new DeployTableJoinColumn(localPrimaryKey, foreignColumn, false, false)); - - DeployTableJoin inverse = prop.getTableJoin().createInverse(baseBeanTable.getBaseTable()); - factory.addPrimaryKeyJoin(prop, inverse); } private void readEmbedded(DeployBeanPropertyAssocOne prop, Embedded embedded) { diff --git a/src/test/java/org/tests/model/onetoone/OtoUBPrime.java b/src/test/java/org/tests/model/onetoone/OtoUBPrime.java new file mode 100644 index 000000000..9416135c6 --- /dev/null +++ b/src/test/java/org/tests/model/onetoone/OtoUBPrime.java @@ -0,0 +1,65 @@ +package org.tests.model.onetoone; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.Version; +import java.util.UUID; + +@Entity +public class OtoUBPrime { + + @Id + UUID pid; + + String name; + + /** + * Master side of bi-directional PrimaryJoinColumn. + */ + @OneToOne(mappedBy = "prime") + OtoUBPrimeExtra extra; + + @Version + Long version; + + public OtoUBPrime(String name) { + this.name = name; + } + + public String toString() { + return "id:"+ pid +" name:"+name+" extra:"+extra; + } + + public UUID getPid() { + return pid; + } + + public void setPid(UUID pid) { + this.pid = pid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public OtoUBPrimeExtra getExtra() { + return extra; + } + + public void setExtra(OtoUBPrimeExtra extra) { + this.extra = extra; + } + + public Long getVersion() { + return version; + } + + public void setVersion(Long version) { + this.version = version; + } +} diff --git a/src/test/java/org/tests/model/onetoone/OtoUBPrimeExtra.java b/src/test/java/org/tests/model/onetoone/OtoUBPrimeExtra.java new file mode 100644 index 000000000..3b50b73f8 --- /dev/null +++ b/src/test/java/org/tests/model/onetoone/OtoUBPrimeExtra.java @@ -0,0 +1,67 @@ +package org.tests.model.onetoone; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.PrimaryKeyJoinColumn; +import javax.persistence.Version; +import java.util.UUID; + +@Entity +public class OtoUBPrimeExtra { + + @Id + UUID eid; + + String extra; + + /** + * Child side of bi-directional PrimaryJoinColumn. + */ + @OneToOne + @PrimaryKeyJoinColumn + OtoUBPrime prime; + + @Version + Long version; + + public OtoUBPrimeExtra(String extra) { + this.extra = extra; + } + + public String toString() { + return "exId:" + eid + " " + extra; + } + + public UUID getEid() { + return eid; + } + + public void setEid(UUID eid) { + this.eid = eid; + } + + public String getExtra() { + return extra; + } + + public void setExtra(String extra) { + this.extra = extra; + } + + public OtoUBPrime getPrime() { + return prime; + } + + public void setPrime(OtoUBPrime prime) { + this.prime = prime; + } + + public Long getVersion() { + return version; + } + + public void setVersion(Long version) { + this.version = version; + } +} diff --git a/src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoinBidi.java b/src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoinBidi.java new file mode 100644 index 000000000..8becdfe02 --- /dev/null +++ b/src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoinBidi.java @@ -0,0 +1,87 @@ +package org.tests.model.onetoone; + +import io.ebean.BaseTestCase; +import io.ebean.Ebean; +import io.ebean.Query; +import org.ebeantest.LoggedSqlCollector; +import org.junit.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestOneToOnePrimaryKeyJoinBidi extends BaseTestCase { + + + private OtoUBPrime insert(String desc) { + OtoUBPrime prime = new OtoUBPrime("u" + desc); + OtoUBPrimeExtra extra = new OtoUBPrimeExtra("v" + desc); + prime.setExtra(extra); + Ebean.save(prime); + return prime; + } + + @Test + public void insertUpdateDelete() { + + String desc = "" + System.currentTimeMillis(); + OtoUBPrime p1 = insert(desc); + + assertThat(p1.getExtra().getEid()).isEqualTo(p1.getPid()).as("Same id values"); + + + Query query = Ebean.find(OtoUBPrime.class).setId(p1.getPid()); + + OtoUBPrime found = query.findOne(); + + assertThat(found).isNotNull(); + assertThat(sqlOf(query)).contains("select t0.pid, t0.name, t0.version, t0.pid from oto_ubprime t0 where t0.pid = ?") + .as("we don't join to oto_ubprime_extra"); + + assertThat(found.getName()).isEqualTo("u" + desc); + + Query queryWithFetch = Ebean.find(OtoUBPrime.class) + .setId(p1.getPid()) + .fetch("extra"); + + OtoUBPrime oneWith = queryWithFetch.findOne(); + + assertThat(oneWith).isNotNull(); + assertThat(sqlOf(queryWithFetch)).contains("select t0.pid, t0.name, t0.version, t1.eid, t1.extra, t1.version, t1.eid from oto_ubprime t0 left join oto_ubprime_extra t1 on t1.eid = t0.pid where t0.pid = ?") + .as("we join to oto_prime_extra"); + + + assertThat(oneWith.getExtra().getExtra()).isEqualTo("v" + desc); + + thenUpdate(oneWith); + thenDelete(found); + } + + private void thenUpdate(OtoUBPrime oneWith) { + + OtoUBPrimeExtra extra = oneWith.getExtra(); + extra.setExtra("modified"); + + Ebean.save(oneWith); + + extra.setExtra("mod2"); + oneWith.setName("mod2"); + + Ebean.save(oneWith); + } + + private void thenDelete(OtoUBPrime found) { + + + OtoUBPrime bean = Ebean.find(OtoUBPrime.class, found.getPid()); + + LoggedSqlCollector.start(); + Ebean.delete(bean); + + List sql = LoggedSqlCollector.stop(); + assertThat(sql).hasSize(2); + assertThat(sql.get(0)).contains("delete from oto_ubprime_extra where"); + assertThat(sql.get(1)).contains("delete from oto_ubprime where"); + + } +}