OPENJPA-235. Reformatted code to meet OpenJPA conventions; widened some type arguments that seemed unnecessarily narrow.

This passes all the OpenJPA tests in my environment, and the logic seems sound. I think that we could adjust the algorithm to require less collection copying, but I don't think that we should hold up the commit for that type of optimization.

git-svn-id: https://svn.apache.org/repos/asf/incubator/openjpa/trunk@535379 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Patrick Linskey 2007-05-04 20:58:49 +00:00
parent 33aa3cb59f
commit 80671afcbf
6 changed files with 521 additions and 10 deletions

View File

@ -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.kernel;
@ -22,8 +22,13 @@ import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;
import java.util.Map;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.ForeignKey;
import org.apache.openjpa.jdbc.sql.PrimaryRow;
import org.apache.openjpa.jdbc.sql.Row;
@ -34,7 +39,7 @@ import org.apache.openjpa.jdbc.sql.SQLExceptions;
import org.apache.openjpa.kernel.OpenJPAStateManager;
/**
* Update manager that writes SQL in object-level operation order.
* Update manager that writes SQL in object-level operation order
*
* @author Abe White
*/
@ -66,13 +71,20 @@ public class OperationOrderUpdateManager
// now do any 'all row' updates, which typically null keys
flush(rmimpl.getAllRowUpdates(), psMgr);
// map statemanagers to primaryrows
Map smMap = mapStateManagers(rmimpl.getOrdered());
// order rows to avoid constraint violations
List orderedRows = orderRows(rmimpl, smMap);
// gather any updates we need to avoid fk constraints on deletes
Collection constraintUpdates = null;
for (Iterator itr = rmimpl.getDeletes().iterator(); itr.hasNext();) {
for (Iterator itr = orderedRows.iterator(); itr.hasNext();) {
try {
constraintUpdates = analyzeDeleteConstraints(rmimpl,
(PrimaryRow) itr.next(), constraintUpdates);
(PrimaryRow) itr.next(), constraintUpdates, smMap,
orderedRows);
} catch (SQLException se) {
exceps = addException(exceps, SQLExceptions.getStore
(se, dict));
@ -82,17 +94,18 @@ public class OperationOrderUpdateManager
flush(constraintUpdates, psMgr);
constraintUpdates.clear();
}
// flush primary rows in order
for (Iterator itr = rmimpl.getOrdered().iterator(); itr.hasNext();) {
for (Iterator itr = orderedRows.iterator(); itr.hasNext();) {
try {
constraintUpdates = flushPrimaryRow(rmimpl, (PrimaryRow)
itr.next(), psMgr, constraintUpdates);
itr.next(), psMgr, constraintUpdates, smMap, orderedRows);
} catch (SQLException se) {
exceps = addException(exceps, SQLExceptions.getStore
(se, dict));
}
}
if (constraintUpdates != null)
flush(constraintUpdates, psMgr);
@ -106,14 +119,122 @@ public class OperationOrderUpdateManager
return exceps;
}
/**
* Reorders all rows provided by the specified RowManagerImpl such that
* no foreign key constraints are violated (assuming a proper schema).
* @param rmimpl RowManagerImpl
*/
private List orderRows(RowManagerImpl rmimpl, Map smMap) {
List orderedRows = new ArrayList();
if (rmimpl.getOrdered().size() > 0) {
List inserts = new ArrayList(rmimpl.getInserts());
List updates = new ArrayList(rmimpl.getUpdates());
List deletes = new ArrayList(rmimpl.getDeletes());
orderedRows.addAll(orderRows(inserts, smMap));
orderedRows.addAll(updates);
orderedRows.addAll(orderRows(deletes, smMap));
}
return orderedRows;
}
private List orderRows(List unorderedList, Map smMap) {
List orderedList = new ArrayList();
// this iterates in a while loop instead of with an iterator to
// avoid ConcurrentModificationExceptions, as unorderedList is
// mutated in the orderRow() invocation.
while (!unorderedList.isEmpty()) {
PrimaryRow nextRow = (PrimaryRow) unorderedList.get(0);
orderRow(nextRow, unorderedList, orderedList, smMap, new Stack());
}
return orderedList;
}
private void orderRow(PrimaryRow currentRow, Collection unordered,
List orderedList, Map smMap, Stack visitedRows) {
if (orderedList.contains(currentRow)) {
return;
}
// a circular reference found which means there is a problem
// with the underlying database schema and/or class metadata
// definitions. nothing can be done here to correct the problem.
if (visitedRows.contains(currentRow)) {
orderedList.addAll(unordered);
unordered.clear();
return;
}
if (currentRow.getAction() == Row.ACTION_INSERT) {
ForeignKey[] fks = currentRow.getTable().getForeignKeys();
OpenJPAStateManager sm;
for (int i = 0; i < fks.length; i++) {
sm = currentRow.getForeignKeySet(fks[i]);
if (sm == null)
continue;
// if the foreign key is new and it's primary key is
// auto assigned
PrimaryRow fkRow = (PrimaryRow) smMap.get(sm);
if (fkRow.getAction() == Row.ACTION_INSERT) {
boolean nullable = true;
Column[] columns = fks[i].getColumns();
for (int j = 0; j < columns.length; j++) {
if (columns[j].isNotNull()) {
nullable = false;
break;
}
}
if (!nullable) {
visitedRows.push(currentRow);
PrimaryRow nextRow = (PrimaryRow) smMap.get(sm);
orderRow(nextRow, unordered, orderedList, smMap,
visitedRows);
visitedRows.pop();
}
}
}
if (!orderedList.contains(currentRow)) {
unordered.remove(currentRow);
orderedList.add(currentRow);
}
} else if (currentRow.getAction() == Row.ACTION_DELETE) {
ForeignKey[] fks = currentRow.getTable().getForeignKeys();
OpenJPAStateManager sm;
for (int i = 0; i < fks.length; i++) {
sm = currentRow.getForeignKeySet(fks[i]);
if (sm == null)
continue;
PrimaryRow fkRow = (PrimaryRow) smMap.get(sm);
// if the foreign key is going to be deleted
if (!orderedList.contains(fkRow)
&& fkRow.getAction() == Row.ACTION_DELETE) {
visitedRows.add(currentRow);
orderRow(fkRow, unordered, orderedList, smMap, visitedRows);
visitedRows.remove(currentRow);
}
}
unordered.remove(currentRow);
orderedList.add(0, currentRow);
}
}
private Map mapStateManagers(List rowList) {
Map smMap = new HashMap();
for (Iterator iter = rowList.iterator(); iter.hasNext();) {
PrimaryRow row = (PrimaryRow) iter.next();
smMap.put(row.getPrimaryKey(), row);
}
return smMap;
}
/**
* Analyze the delete constraints on the given row, gathering necessary
* updates to null fks before deleting.
*/
private Collection analyzeDeleteConstraints(RowManagerImpl rowMgr,
PrimaryRow row, Collection updates)
PrimaryRow row, Collection updates, Map smMap, List orderedRows)
throws SQLException {
if (!row.isValid())
if (!row.isValid() || row.getAction() != Row.ACTION_DELETE)
return updates;
ForeignKey[] fks = row.getTable().getForeignKeys();
@ -127,6 +248,11 @@ public class OperationOrderUpdateManager
sm = row.getForeignKeyWhere(fks[i]);
if (sm == null)
continue;
PrimaryRow fkRow = (PrimaryRow) smMap.get(sm);
int fkIndex = orderedRows.indexOf(fkRow);
int rIndex = orderedRows.indexOf(row);
if (fkIndex > rIndex)
continue;
// only need an update if we have an fk to a row that's being
// deleted before we are
@ -152,7 +278,8 @@ public class OperationOrderUpdateManager
* Flush the given row, creating deferred updates for dependencies.
*/
private Collection flushPrimaryRow(RowManagerImpl rowMgr, PrimaryRow row,
PreparedStatementManager psMgr, Collection updates)
PreparedStatementManager psMgr, Collection updates, Map smMap,
List orderedRows)
throws SQLException {
if (!row.isValid())
return updates;
@ -172,6 +299,13 @@ public class OperationOrderUpdateManager
if (sm == null)
continue;
PrimaryRow fkRow = (PrimaryRow) smMap.get(sm);
int fkIndex = orderedRows.indexOf(fkRow);
int rIndex = orderedRows.indexOf(row);
// consider sm flushed, no need to defer
if (rIndex > fkIndex)
continue;
// only need an update if we have an fk to a row that's being
// inserted after we are; if row is dependent on itself and no
// fk, must be an auto-inc because otherwise we wouldn't have

View File

@ -0,0 +1,79 @@
/*
* 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.jdbc.kernel;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Version;
import org.apache.openjpa.persistence.jdbc.ForeignKey;
@Entity
public class EntityA {
@Id
@Column(name = "entitya_id", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@OneToOne(cascade = CascadeType.ALL, optional = false)
@JoinColumn(name = "entityb_id", referencedColumnName = "entityb_id",
nullable = false)
@ForeignKey
private EntityB entityB;
@Version
private Integer optLock;
public EntityA() {
}
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public EntityB getEntityB() {
return this.entityB;
}
public void setEntityB(EntityB entityB) {
this.entityB = entityB;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,79 @@
/*
* 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.jdbc.kernel;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Version;
import org.apache.openjpa.persistence.jdbc.ForeignKey;
@Entity
public class EntityB {
@Id
@Column(name = "entityb_id", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@OneToOne(cascade = CascadeType.ALL, optional = false)
@JoinColumn(name = "entityc_id", referencedColumnName = "entityc_id",
nullable = false)
@ForeignKey
private EntityC entityC;
@Version
private Integer optLock;
public EntityB() {
}
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public EntityC getEntityC() {
return this.entityC;
}
public void setEntityC(EntityC entityC) {
this.entityC = entityC;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,81 @@
/*
* 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.jdbc.kernel;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Version;
import org.apache.openjpa.persistence.Dependent;
import org.apache.openjpa.persistence.jdbc.ForeignKey;
@Entity
public class EntityC {
@Id
@Column(name = "entityc_id", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@OneToOne(cascade = CascadeType.ALL, optional = false)
@JoinColumn(name = "entityd_id", referencedColumnName = "entityd_id",
nullable = false)
@ForeignKey
@Dependent
private EntityD entityD;
@Version
private Integer optLock;
public EntityC() {
}
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public EntityD getEntityD() {
return this.entityD;
}
public void setEntityD(EntityD entityD) {
this.entityD = entityD;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,60 @@
/*
* 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.jdbc.kernel;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Version;
@Entity
public class EntityD {
@Id
@Column(name = "entityd_id", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@Version
private Integer optLock;
public EntityD() {
}
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,78 @@
/*
* 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.jdbc.kernel;
import javax.persistence.EntityManager;
import junit.textui.TestRunner;
import org.apache.openjpa.persistence.test.SingleEMFTestCase;
/**
* Test that sql statements get flushed in an order which does not violate
* non-nullable foreign key constraints on inserts and deletes.
*
* @author Reece Garrett
*/
public class TestNoForeignKeyViolation
extends SingleEMFTestCase {
private EntityA entityA;
private EntityC entityC;
public void setUp() {
setUp(EntityA.class, EntityB.class, EntityC.class, EntityD.class);
entityA = new EntityA();
EntityB entityB = new EntityB();
entityC = new EntityC();
EntityD entityD = new EntityD();
entityA.setName("entityA");
entityB.setName("entityB");
entityC.setName("entityC");
entityD.setName("entityD");
entityA.setEntityB(entityB);
entityB.setEntityC(entityC);
entityC.setEntityD(entityD);
}
public void testSqlOrder() {
EntityManager em = emf.createEntityManager();
try {
em.getTransaction().begin();
em.persist(entityA);
em.getTransaction().commit();
EntityD newEntityD = new EntityD();
newEntityD.setName("newEntityD");
entityC.setEntityD(newEntityD);
em.getTransaction().begin();
em.merge(entityC);
em.getTransaction().commit();
}
finally {
em.close();
}
}
public static void main(String[] args) {
TestRunner.run(TestNoForeignKeyViolation.class);
}
}