diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java index bb4fab41b..be940861b 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssoc.java @@ -357,14 +357,34 @@ public abstract class BeanPropertyAssoc extends BeanProperty implements STree * Return true if this association is updateable. */ public boolean isUpdateable() { - return tableJoin.columns().length <= 0 || tableJoin.columns()[0].isUpdateable(); + TableJoinColumn[] columns = tableJoin.columns(); + if (columns.length <= 0) { + return true; + } + for (TableJoinColumn column : columns) { + if (column.isUpdateable()) { + // at least 1 is updatable + return true; + } + } + return false; } /** * Return true if this association is insertable. */ public boolean isInsertable() { - return tableJoin.columns().length <= 0 || tableJoin.columns()[0].isInsertable(); + TableJoinColumn[] columns = tableJoin.columns(); + if (columns.length <= 0) { + return true; + } + for (TableJoinColumn column : columns) { + if (column.isInsertable()) { + // at least 1 is insertable + return true; + } + } + return false; } /** @@ -443,16 +463,18 @@ public abstract class BeanPropertyAssoc extends BeanProperty implements STree String matchColumn = col.getForeignDbColumn(); String localColumn = col.getLocalDbColumn(); String localSqlFormula = col.getLocalSqlFormula(); + boolean insertable = col.isInsertable(); + boolean updateable = col.isUpdateable(); for (int j = 0; j < props.length; j++) { if (props[j].getDbColumn().equalsIgnoreCase(matchColumn)) { - return new ImportedIdSimple(owner, localColumn, localSqlFormula, props[j], j); + return new ImportedIdSimple(owner, localColumn, localSqlFormula, props[j], j, insertable, updateable); } } for (int j = 0; j < others.length; j++) { if (others[j].getDbColumn().equalsIgnoreCase(matchColumn)) { - return new ImportedIdSimple(owner, localColumn, localSqlFormula, others[j], j + props.length); + return new ImportedIdSimple(owner, localColumn, localSqlFormula, others[j], j + props.length, insertable, updateable); } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdEmbedded.java b/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdEmbedded.java index 22c80dfc9..d22916da3 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdEmbedded.java +++ b/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdEmbedded.java @@ -64,8 +64,11 @@ public class ImportedIdEmbedded implements ImportedId { @Override public void dmlAppend(GenerateDmlRequest request) { + boolean update = request.isUpdate(); for (ImportedIdSimple anImported : imported) { - request.appendColumn(anImported.localDbColumn); + if (anImported.isInclude(update)) { + request.appendColumn(anImported.localDbColumn); + } } } @@ -100,30 +103,28 @@ public class ImportedIdEmbedded implements ImportedId { @Override public Object bind(BindableRequest request, EntityBean bean) throws SQLException { - Object embeddedId = null; - - if (bean != null) { - embeddedId = foreignAssocOne.getValue(bean); - } + Object embeddedId = (bean == null) ? null : foreignAssocOne.getValue(bean); + boolean update = request.isUpdate(); if (embeddedId == null) { for (ImportedIdSimple anImported : imported) { - if (anImported.owner.isUpdateable()) { + if (anImported.isInclude(update)) { request.bind(null, anImported.foreignProperty); } } + // return anything non-null to skip a derived relationship update + return Object.class; } else { EntityBean embedded = (EntityBean) embeddedId; for (ImportedIdSimple anImported : imported) { - if (anImported.owner.isUpdateable()) { + if (anImported.isInclude(update)) { Object scalarValue = anImported.foreignProperty.getValue(embedded); request.bind(scalarValue, anImported.foreignProperty); } } + return embedded; } - // hmmm, not worrying about this just yet - return null; } @Override diff --git a/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdSimple.java b/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdSimple.java index e84b07e49..2ff4b60b1 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdSimple.java +++ b/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdSimple.java @@ -47,15 +47,32 @@ public final class ImportedIdSimple implements ImportedId, Comparable owner, String localDbColumn, String localSqlFormula, BeanProperty foreignProperty, int position) { + /** + * If true include in insert. + */ + private final boolean insertable; + + /** + * If true include in update. + */ + private final boolean updateable; + + public ImportedIdSimple(BeanPropertyAssoc owner, String localDbColumn, String localSqlFormula, BeanProperty foreignProperty, int position, + boolean insertable, boolean updateable) { this.owner = owner; this.localDbColumn = InternString.intern(localDbColumn); this.localSqlFormula = InternString.intern(localSqlFormula); this.foreignProperty = foreignProperty; this.position = position; + this.insertable = insertable; + this.updateable = updateable; this.logicalName = InternString.intern(owner.getName() + "." + foreignProperty.getName()); } + public ImportedIdSimple(BeanPropertyAssoc owner, String localDbColumn, String localSqlFormula, BeanProperty foreignProperty, int position) { + this(owner, localDbColumn, localSqlFormula, foreignProperty, position, true, true); + } + /** * Return the list as an array sorted into the same order as the Bean Properties. */ @@ -68,6 +85,13 @@ public final class ImportedIdSimple implements ImportedId, Comparable 0) ? "?" : "=?"; appendColumn(column, "?"); @@ -91,4 +87,7 @@ public class GenerateDmlRequest { this.prefix2 = ", "; } + public boolean isUpdate() { + return insertMode == 0; + } } 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 babc0ab39..8e7246730 100644 --- a/src/main/java/io/ebeaninternal/server/persist/dml/InsertHandler.java +++ b/src/main/java/io/ebeaninternal/server/persist/dml/InsertHandler.java @@ -50,6 +50,11 @@ public class InsertHandler extends DmlHandler { this.concatinatedKey = meta.isConcatenatedKey(); } + @Override + public boolean isUpdate() { + return false; + } + /** * Generate and bind the insert statement. */ diff --git a/src/main/java/io/ebeaninternal/server/persist/dml/UpdateHandler.java b/src/main/java/io/ebeaninternal/server/persist/dml/UpdateHandler.java index 2f1082046..772396618 100644 --- a/src/main/java/io/ebeaninternal/server/persist/dml/UpdateHandler.java +++ b/src/main/java/io/ebeaninternal/server/persist/dml/UpdateHandler.java @@ -22,6 +22,11 @@ public class UpdateHandler extends DmlHandler { this.meta = meta; } + @Override + public boolean isUpdate() { + return true; + } + /** * Generate and bind the update statement. */ diff --git a/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindableRequest.java b/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindableRequest.java index a3f497b4e..66a0abf2a 100644 --- a/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindableRequest.java +++ b/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindableRequest.java @@ -59,4 +59,8 @@ public interface BindableRequest { */ long now(); + /** + * Return true if this is an update request. + */ + boolean isUpdate(); } diff --git a/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryAssocOnes.java b/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryAssocOnes.java index 78b0c558d..bf3b0723d 100644 --- a/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryAssocOnes.java +++ b/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryAssocOnes.java @@ -19,9 +19,7 @@ public class FactoryAssocOnes { */ public void create(List list, BeanDescriptor desc, DmlMode mode) { - BeanPropertyAssocOne[] ones = desc.propertiesOneImported(); - - for (BeanPropertyAssocOne one : ones) { + for (BeanPropertyAssocOne one : desc.propertiesOneImported()) { if (!one.isImportedPrimaryKey()) { switch (mode) { case INSERT: diff --git a/src/test/java/org/tests/model/composite/CkeClient.java b/src/test/java/org/tests/model/composite/CkeClient.java new file mode 100644 index 000000000..59cd53edb --- /dev/null +++ b/src/test/java/org/tests/model/composite/CkeClient.java @@ -0,0 +1,48 @@ +package org.tests.model.composite; + +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.JoinColumns; +import javax.persistence.ManyToOne; + +@Entity +public class CkeClient { + + @EmbeddedId + private CkeClientKey clientPK; + + @JoinColumns({ + @JoinColumn(name = "username", referencedColumnName = "username"), + @JoinColumn(name = "cod_cpny", referencedColumnName = "cod_cpny", insertable = false, updatable = false) + }) + @ManyToOne(optional = false, fetch = FetchType.LAZY) + private CkeUser user; + + private String notes; + + public CkeUser getUser() { + return user; + } + + public void setUser(CkeUser user) { + this.user = user; + } + + public CkeClientKey getClientPK() { + return clientPK; + } + + public void setClientPK(CkeClientKey clientPK) { + this.clientPK = clientPK; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } +} diff --git a/src/test/java/org/tests/model/composite/CkeClientKey.java b/src/test/java/org/tests/model/composite/CkeClientKey.java new file mode 100644 index 000000000..c32bdb10f --- /dev/null +++ b/src/test/java/org/tests/model/composite/CkeClientKey.java @@ -0,0 +1,53 @@ +package org.tests.model.composite; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Embeddable; +import java.util.Objects; + +@Embeddable +public class CkeClientKey { + + @Basic(optional = false) + @Column(name = "cod_cpny") + private int codCompany; + + @Basic(optional = false) + @Column(name = "cod_client") + private String codClient; + + public CkeClientKey(int codCompany, String codClient) { + this.codCompany = codCompany; + this.codClient = codClient; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CkeClientKey that = (CkeClientKey) o; + return codCompany == that.codCompany && + Objects.equals(codClient, that.codClient); + } + + @Override + public int hashCode() { + return Objects.hash(codCompany, codClient); + } + + public int getCodCompany() { + return codCompany; + } + + public void setCodCompany(int codCompany) { + this.codCompany = codCompany; + } + + public String getCodClient() { + return codClient; + } + + public void setCodClient(String codClient) { + this.codClient = codClient; + } +} diff --git a/src/test/java/org/tests/model/composite/CkeUser.java b/src/test/java/org/tests/model/composite/CkeUser.java new file mode 100644 index 000000000..1c9de8ccc --- /dev/null +++ b/src/test/java/org/tests/model/composite/CkeUser.java @@ -0,0 +1,29 @@ +package org.tests.model.composite; + +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; + +@Entity +public class CkeUser { + + @EmbeddedId + private CkeUserKey userPK; + + private String name; + + public CkeUserKey getUserPK() { + return userPK; + } + + public void setUserPK(CkeUserKey userPK) { + this.userPK = userPK; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/test/java/org/tests/model/composite/CkeUserKey.java b/src/test/java/org/tests/model/composite/CkeUserKey.java new file mode 100644 index 000000000..cb7655e19 --- /dev/null +++ b/src/test/java/org/tests/model/composite/CkeUserKey.java @@ -0,0 +1,53 @@ +package org.tests.model.composite; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Embeddable; +import java.util.Objects; + +@Embeddable +public class CkeUserKey { + + @Basic(optional = false) + @Column(name = "cod_cpny") + private int codCompany; + + @Basic(optional = false) + @Column(name = "username") + private String username; + + public CkeUserKey(int codCompany, String username) { + this.codCompany = codCompany; + this.username = username; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CkeUserKey that = (CkeUserKey) o; + return codCompany == that.codCompany && + Objects.equals(username, that.username); + } + + @Override + public int hashCode() { + return Objects.hash(codCompany, username); + } + + public int getCodCompany() { + return codCompany; + } + + public void setCodCompany(int codCompany) { + this.codCompany = codCompany; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } +} diff --git a/src/test/java/org/tests/model/composite/TestCompositeKeyUserClient.java b/src/test/java/org/tests/model/composite/TestCompositeKeyUserClient.java new file mode 100644 index 000000000..cf4d59e09 --- /dev/null +++ b/src/test/java/org/tests/model/composite/TestCompositeKeyUserClient.java @@ -0,0 +1,50 @@ +package org.tests.model.composite; + +import io.ebean.BaseTestCase; +import io.ebean.Ebean; +import org.ebeantest.LoggedSqlCollector; +import org.junit.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestCompositeKeyUserClient extends BaseTestCase { + + @Test + public void test() { + + CkeUser user0 = new CkeUser(); + user0.setUserPK(new CkeUserKey(20, "sally")); + user0.setName("sally"); + Ebean.save(user0); + + CkeUser user1 = new CkeUser(); + user1.setUserPK(new CkeUserKey(20, "frank")); + user1.setName("hello"); + Ebean.save(user1); + + CkeClient client = new CkeClient(); + client.setNotes("try it"); + client.setClientPK(new CkeClientKey(20, "susan")); + client.setUser(user1); + + LoggedSqlCollector.start(); + + Ebean.save(client); + + client.setNotes("update it"); + client.setUser(user0); + + Ebean.save(client); + + List sql = LoggedSqlCollector.stop(); + + assertThat(sql).hasSize(2); + assertThat(sql.get(0)).contains("insert into cke_client (cod_cpny, cod_client, notes, username) values (?,?,?,?)"); + assertThat(sql.get(1)).contains("update cke_client set notes=?, username=? where cod_cpny=? and cod_client=?"); + + Ebean.delete(client); + + } +}