You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by pc...@apache.org on 2007/05/04 22:58:50 UTC

svn commit: r535379 - in /incubator/openjpa/trunk: openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/ openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/

Author: pcl
Date: Fri May  4 13:58:49 2007
New Revision: 535379

URL: http://svn.apache.org/viewvc?view=rev&rev=535379
Log:
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.

Added:
    incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/
    incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/EntityA.java
    incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/EntityB.java
    incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/EntityC.java
    incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/EntityD.java
    incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/TestNoForeignKeyViolation.java
Modified:
    incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/OperationOrderUpdateManager.java

Modified: incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/OperationOrderUpdateManager.java
URL: http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/OperationOrderUpdateManager.java?view=diff&rev=535379&r1=535378&r2=535379
==============================================================================
--- incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/OperationOrderUpdateManager.java (original)
+++ incubator/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/OperationOrderUpdateManager.java Fri May  4 13:58:49 2007
@@ -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.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.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 @@
 
         // 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 @@
             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);
 
@@ -107,13 +120,121 @@
     }
 
     /**
+     * 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 @@
                 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 @@
      * 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;
@@ -170,6 +297,13 @@
         for (int i = 0; i < fks.length; i++) {
             sm = row.getForeignKeySet(fks[i]);
             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

Added: incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/EntityA.java
URL: http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/EntityA.java?view=auto&rev=535379
==============================================================================
--- incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/EntityA.java (added)
+++ incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/EntityA.java Fri May  4 13:58:49 2007
@@ -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;
+    }
+}
+

Added: incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/EntityB.java
URL: http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/EntityB.java?view=auto&rev=535379
==============================================================================
--- incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/EntityB.java (added)
+++ incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/EntityB.java Fri May  4 13:58:49 2007
@@ -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;
+    }
+}
+

Added: incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/EntityC.java
URL: http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/EntityC.java?view=auto&rev=535379
==============================================================================
--- incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/EntityC.java (added)
+++ incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/EntityC.java Fri May  4 13:58:49 2007
@@ -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;
+    }
+}
+

Added: incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/EntityD.java
URL: http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/EntityD.java?view=auto&rev=535379
==============================================================================
--- incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/EntityD.java (added)
+++ incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/EntityD.java Fri May  4 13:58:49 2007
@@ -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;
+    }
+}
+

Added: incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/TestNoForeignKeyViolation.java
URL: http://svn.apache.org/viewvc/incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/TestNoForeignKeyViolation.java?view=auto&rev=535379
==============================================================================
--- incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/TestNoForeignKeyViolation.java (added)
+++ incubator/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/kernel/TestNoForeignKeyViolation.java Fri May  4 13:58:49 2007
@@ -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);
+    }
+}