mirror of https://github.com/apache/openjpa.git
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:
parent
a325f8a224
commit
970cd53e8d
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue