OPENJPA-2525: Use of JoinColumn targets to another joinColumn key exposed as an attribute will cause a ConstrainViolation exception on persist

git-svn-id: https://svn.apache.org/repos/asf/openjpa/branches/2.3.x@1627112 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Jody Grassel 2014-09-23 18:34:08 +00:00
parent a325f8a224
commit 970cd53e8d
5 changed files with 363 additions and 11 deletions

View File

@ -14,7 +14,7 @@
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the * KIND, either express or implied. See the License for the
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
package org.apache.openjpa.jdbc.sql; 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.kernel.JDBCStore;
import org.apache.openjpa.jdbc.meta.ClassMapping; 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.JavaSQLTypes;
import org.apache.openjpa.jdbc.meta.Joinable; import org.apache.openjpa.jdbc.meta.Joinable;
import org.apache.openjpa.jdbc.meta.RelationId; 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.ForeignKey;
import org.apache.openjpa.jdbc.schema.Table; import org.apache.openjpa.jdbc.schema.Table;
import org.apache.openjpa.kernel.OpenJPAStateManager; 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.meta.JavaTypes;
import org.apache.openjpa.util.InternalException; import org.apache.openjpa.util.InternalException;
@ -284,15 +287,16 @@ public class RowImpl
else else
val = join.getJoinValue(to, toCols[i], (JDBCStore) to. val = join.getJoinValue(to, toCols[i], (JDBCStore) to.
getContext().getStoreManager().getInnermostDelegate()); getContext().getStoreManager().getInnermostDelegate());
if (set && val == null) { if (set && val == null) {
if (canSet(io, i, true)) if (canSet(io, i, true))
setNull(fromCols[i]); setNull(fromCols[i]);
} else if (set && val instanceof Raw) } else if (set && val instanceof Raw)
setRaw(fromCols[i], val.toString()); setRaw(fromCols[i], val.toString());
else if (set) else if (set) {
setObject(fromCols[i], val, toCols[i].getJavaType(), false); 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]); whereNull(fromCols[i]);
else if (val instanceof Raw) else if (val instanceof Raw)
whereRaw(fromCols[i], val.toString()); 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. * 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 // never set auto increment columns and honor column defaults
if (_action == ACTION_INSERT) { if (_action == ACTION_INSERT) {
if (col.isAutoAssigned()) { if (col.isAutoAssigned()) {
// OPENJPA-349: validate because this can be the only column // OPENJPA-349: validate because this can be the only column
setValid(true); setValid(true);
return; return;
} }
if (!overrideDefault && val == null if (!overrideDefault && val == null
&& col.getDefaultString() != null) && col.getDefaultString() != null)
return; return;
} }
@ -959,19 +994,19 @@ public class RowImpl
if (isValid()) if (isValid())
row.setValid(true); row.setValid(true);
} }
public Object[] getVals() { public Object[] getVals() {
return _vals; return _vals;
} }
public int[] getTypes() { public int[] getTypes() {
return _types; return _types;
} }
public boolean isFlushed() { public boolean isFlushed() {
return _isFlushed; return _isFlushed;
} }
public void setFlushed(boolean isFlushed) { public void setFlushed(boolean isFlushed) {
_isFlushed = isFlushed; _isFlushed = isFlushed;
} }

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<AEvident> aEvidents = new HashSet<AEvident>();
@OneToMany(targetEntity=AEvident.class, mappedBy="aText", cascade=CascadeType.MERGE)
public Set<AEvident> getAEvidents() {
return aEvidents;
}
public void setAEvidents(Set<AEvident> 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;
}
}

View File

@ -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());
}
}