diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/RowImpl.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/RowImpl.java index 794426443..68272b6ae 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/RowImpl.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/RowImpl.java @@ -14,7 +14,7 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ package org.apache.openjpa.jdbc.sql; @@ -35,6 +35,7 @@ import java.util.Locale; import org.apache.openjpa.jdbc.kernel.JDBCStore; import org.apache.openjpa.jdbc.meta.ClassMapping; +import org.apache.openjpa.jdbc.meta.FieldMapping; import org.apache.openjpa.jdbc.meta.JavaSQLTypes; import org.apache.openjpa.jdbc.meta.Joinable; import org.apache.openjpa.jdbc.meta.RelationId; @@ -43,6 +44,8 @@ import org.apache.openjpa.jdbc.schema.ColumnIO; import org.apache.openjpa.jdbc.schema.ForeignKey; import org.apache.openjpa.jdbc.schema.Table; import org.apache.openjpa.kernel.OpenJPAStateManager; +import org.apache.openjpa.meta.ClassMetaData; +import org.apache.openjpa.meta.FieldMetaData; import org.apache.openjpa.meta.JavaTypes; import org.apache.openjpa.util.InternalException; @@ -284,15 +287,16 @@ public class RowImpl else val = join.getJoinValue(to, toCols[i], (JDBCStore) to. getContext().getStoreManager().getInnermostDelegate()); - + if (set && val == null) { if (canSet(io, i, true)) setNull(fromCols[i]); } else if (set && val instanceof Raw) setRaw(fromCols[i], val.toString()); - else if (set) + else if (set) { setObject(fromCols[i], val, toCols[i].getJavaType(), false); - else if (val == null) + setJoinRefColumn(to, fromCols, toCols[i], val); + } else if (val == null) whereNull(fromCols[i]); else if (val instanceof Raw) whereRaw(fromCols[i], val.toString()); @@ -301,6 +305,37 @@ public class RowImpl } } + private void setJoinRefColumn(OpenJPAStateManager inverseSm, Column ownerCols[], Column inverseCol, + Object val) { + OpenJPAStateManager ownerSm = getPrimaryKey(); + if (ownerSm != null) { + ClassMetaData ownerMeta = ownerSm.getMetaData(); + // loop through all the fields in the owner entity + for (FieldMetaData ownerFM : ownerMeta.getFields()) { + // look for any single column in this field references the + // same column as the foreign key target column + Column cols[] = ((FieldMapping) ownerFM).getColumns(); + if (cols.length == 1 // only support attribute of non-compound foreign key + && cols != ownerCols // not @Id field + && cols[0].getIdentifier().equals(ownerCols[0].getIdentifier())) { + // copy the foreign key value to the current field. + FieldMetaData inverseFM = inverseSm.getMetaData().getField( + inverseCol.getIdentifier().getName()); + if (inverseFM != null) { + int inverseValIndex = inverseFM.getIndex(); + Class inverseType = inverseSm.getMetaData().getField(inverseValIndex).getType(); + int ownerIndex = ownerFM.getIndex(); + Class ownerType = ownerSm.getMetaData().getField(ownerIndex).getType(); + if (inverseType == ownerType) { + Object inverseVal = inverseSm.fetch(inverseValIndex); + ownerSm.storeField(ownerIndex, inverseVal); + } + } + } + } + } + } + /** * Return true if any of the given column indexes are settable. */ @@ -682,11 +717,11 @@ public class RowImpl // never set auto increment columns and honor column defaults if (_action == ACTION_INSERT) { if (col.isAutoAssigned()) { - // OPENJPA-349: validate because this can be the only column - setValid(true); + // OPENJPA-349: validate because this can be the only column + setValid(true); return; } - if (!overrideDefault && val == null + if (!overrideDefault && val == null && col.getDefaultString() != null) return; } @@ -959,19 +994,19 @@ public class RowImpl if (isValid()) row.setValid(true); } - + public Object[] getVals() { return _vals; } - + public int[] getTypes() { return _types; } - + public boolean isFlushed() { return _isFlushed; } - + public void setFlushed(boolean isFlushed) { _isFlushed = isFlushed; } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/relations/ACase.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/relations/ACase.java new file mode 100644 index 000000000..af2c98b3d --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/relations/ACase.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.persistence.relations; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.OneToOne; + +@Entity +public class ACase { + + private int id; + @Id + @GeneratedValue(strategy=GenerationType.IDENTITY) + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + private String name; + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + private AText aText; + @OneToOne(fetch=FetchType.LAZY, mappedBy="aCase", cascade=CascadeType.MERGE) + public AText getAText() { + return aText; + } + + public void setAText(AText aText) { + this.aText = aText; + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/relations/AEvident.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/relations/AEvident.java new file mode 100644 index 000000000..b6963f365 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/relations/AEvident.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.persistence.relations; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +@Entity +public class AEvident { + + private int id; + @Id + @GeneratedValue(strategy=GenerationType.IDENTITY) + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + private String name; + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + private AText aText; + @ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.MERGE) + @JoinColumn(name="ACASE_ID", referencedColumnName="ACASE_ID") + public AText getAText() { + return aText; + } + + public void setAText(AText aText) { + this.aText = aText; + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/relations/AText.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/relations/AText.java new file mode 100644 index 000000000..4b044cc4e --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/relations/AText.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.persistence.relations; + +import java.util.HashSet; +import java.util.Set; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; + +@Entity +public class AText { + + private int id; + @Id + @GeneratedValue(strategy=GenerationType.IDENTITY) + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + private String name; + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + private ACase aCase; + @OneToOne(fetch=FetchType.LAZY, cascade=CascadeType.MERGE) + @JoinColumn(name="ACASE_ID", nullable=false) + public ACase getACase() { + return aCase; + } + + public void setACase(ACase aCase) { + this.aCase = aCase; + } + + + private Set aEvidents = new HashSet(); + @OneToMany(targetEntity=AEvident.class, mappedBy="aText", cascade=CascadeType.MERGE) + public Set getAEvidents() { + return aEvidents; + } + + public void setAEvidents(Set aEvidents) { + this.aEvidents = aEvidents; + } + + private int aCaseId; + @Column(name="ACASE_ID", insertable=false, updatable=false, unique=true) + public int getACaseId() { + return aCaseId; + } + + public void setACaseId(int aCaseId) { + this.aCaseId = aCaseId; + } + +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/relations/TestO2ORefColumn.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/relations/TestO2ORefColumn.java new file mode 100644 index 000000000..ae48e5498 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/relations/TestO2ORefColumn.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.persistence.relations; + +import javax.persistence.EntityManager; + +import junit.framework.Assert; + +import org.apache.openjpa.persistence.test.SingleEMFTestCase; + +/** + * Unit test to verify the foreign key of a Join column in an association (aText), that is exposed as an + * attribute, is updated after the entity is flushed or committed to the data store. + * See AText -> ACase's foreign key also exposed a non-insertable and non-updatable attribute 'aCaseId'. + * + * In the test case, if the aCaseId is not updated with the foreign key (aCase.id) value, an association + * of aText (aEvident) with JoinColumn(..referencedColumnName=..) overridden to non-standard foreign key, + * a constraint violation will occur when aEvident is persisted to the data store. + */ +public class TestO2ORefColumn extends SingleEMFTestCase { + + public void setUp () { + setUp(CLEAR_TABLES, + ACase.class, AText.class, AEvident.class, + "openjpa.jdbc.MappingDefaults", "ForeignKeyDeleteAction=cascade,JoinForeignKeyDeleteAction=cascade" + ); + } + + public void testRefColumnJoinEntities () { + AEvident aEvident = new AEvident(); + aEvident.setName("Evident_A"); + + AText aText = new AText(); + aText.setName("Text_A"); + aText.getAEvidents().add(aEvident); + aEvident.setAText(aText); + + ACase aCase = new ACase(); + aCase.setName ("Case_A"); + aCase.setAText(aText); + aText.setACase(aCase); + + EntityManager em = emf.createEntityManager (); + em.getTransaction().begin (); + em.persist(aEvident); + em.persist(aText); + em.persist(aCase); + em.getTransaction ().commit (); + + verify(aCase, aText, aEvident); + + em.clear(); + + ACase fACase = em.find(ACase.class, aCase.getId()); + AText fAText = fACase.getAText(); + AEvident fAEvident = fAText.getAEvidents().iterator().next(); + verify(fACase, fAText, fAEvident); + + em.close (); + } + + private void verify(ACase aCase, AText aText, AEvident aEvident) { + Assert.assertNotNull(aCase); + Assert.assertNotNull(aText); + Assert.assertNotNull(aEvident); + + Assert.assertTrue(aCase.getId() != 0); + Assert.assertTrue(aText.getId() != 0); + Assert.assertTrue(aEvident.getId() != 0); + + Assert.assertEquals("Case_A", aCase.getName()); + Assert.assertEquals("Text_A", aText.getName()); + Assert.assertEquals("Evident_A", aEvident.getName()); + + Assert.assertNotNull(aCase.getAText()); + Assert.assertSame(aCase.getAText(), aText); + Assert.assertNotNull(aText.getACase()); + Assert.assertSame(aCase, aText.getACase()); + + Assert.assertEquals(aText.getACaseId(), aCase.getId()); + Assert.assertNotNull(aText.getAEvidents()); + Assert.assertTrue(aText.getAEvidents().iterator().hasNext()); + Assert.assertSame(aEvident, aText.getAEvidents().iterator().next()); + Assert.assertNotNull(aEvident.getAText()); + Assert.assertSame(aText, aEvident.getAText()); + } +}