You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by mi...@apache.org on 2010/03/25 04:55:39 UTC

svn commit: r927266 - in /openjpa/branches/2.0.x: openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/ openjpa-kernel/src/main/java/org/apache/openjpa/kernel/ openjpa-lib/src/main/java/org/apache/openjpa/lib/jdbc/ openjpa-persistence-jdbc/src/tes...

Author: mikedd
Date: Thu Mar 25 03:55:39 2010
New Revision: 927266

URL: http://svn.apache.org/viewvc?rev=927266&view=rev
Log:
OPENJPA-1550:
Set failedObject on RollbackException.
Submitted By: Heath Thomann

Added:
    openjpa/branches/2.0.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/
    openjpa/branches/2.0.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/
    openjpa/branches/2.0.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/Ent1.java   (with props)
    openjpa/branches/2.0.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/TestBatchLimitException.java   (with props)
Modified:
    openjpa/branches/2.0.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/BatchingPreparedStatementManagerImpl.java
    openjpa/branches/2.0.x/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java
    openjpa/branches/2.0.x/openjpa-lib/src/main/java/org/apache/openjpa/lib/jdbc/LoggingConnectionDecorator.java
    openjpa/branches/2.0.x/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java
    openjpa/branches/2.0.x/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/RollbackException.java
    openjpa/branches/2.0.x/openjpa-project/CHANGES.txt
    openjpa/branches/2.0.x/openjpa-project/RELEASE-NOTES.html

Modified: openjpa/branches/2.0.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/BatchingPreparedStatementManagerImpl.java
URL: http://svn.apache.org/viewvc/openjpa/branches/2.0.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/BatchingPreparedStatementManagerImpl.java?rev=927266&r1=927265&r2=927266&view=diff
==============================================================================
--- openjpa/branches/2.0.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/BatchingPreparedStatementManagerImpl.java (original)
+++ openjpa/branches/2.0.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/BatchingPreparedStatementManagerImpl.java Thu Mar 25 03:55:39 2010
@@ -192,12 +192,11 @@ public class BatchingPreparedStatementMa
                 //similar to this path, or I should say, the path which is taken instead of this path when
                 //we aren't using batching), we see that the catch block doesn't do a 'se.getNextException'.
                 //When we do a 'getNextException', the 'next exception' doesn't contain the same message as se.
-                //That is, 'next exception' contains a subset msg which is contained in se.  For legacy, should
-                //we continute to use 'sqex' in the 'old path' and use 'se' in the next path/code?????
-                //SQLException sqex = se.getNextException();
-                //if (sqex == null)
-                //    sqex = se;
-                SQLException sqex = se;
+                //That is, 'next exception' contains a subset msg which is contained in se.
+                SQLException sqex = se.getNextException();
+                if (sqex == null){
+                    sqex = se;
+                }
                 
                 if (se instanceof ReportingSQLException){
                   int index = ((ReportingSQLException) se).getIndexOfFirstFailedObject();
@@ -209,24 +208,23 @@ public class BatchingPreparedStatementMa
                       index = 0;
                   }
                   
-                  //index should not be less than 0 this path, but if for some reason it is, lets
+                  //index should not be less than 0 in this path, but if for some reason it is, lets
                   //resort to the 'old way' and simply pass the 'ps' as the failed object.
                   if (index < 0){ 
-                      throw SQLExceptions.getStore(sqex, ps, _dict);
+                      throw SQLExceptions.getStore(se, ps, _dict);
                   }
                   else{
-                      throw SQLExceptions.getStore(sqex, ((RowImpl)(_batchedRows.get(index))).getFailedObject(), _dict);
+                      throw SQLExceptions.getStore(se, ((RowImpl)(_batchedRows.get(index))).getFailedObject(), _dict);
                   }                    
                 }
                 else{
+                	//per comments above, use 'sqex' rather than 'se'. 
                     throw SQLExceptions.getStore(sqex, ps, _dict);
                 }
             } finally {
                 _batchedSql = null;
                 batchedRows.clear();
                 if (ps != null) {
-                    //Clear the Params now....should this be done above? No. 
-                    //if JDBC provider using PureQuery, ps is null
                     ps.clearParameters();
                     try {
                         ps.close();

Modified: openjpa/branches/2.0.x/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java
URL: http://svn.apache.org/viewvc/openjpa/branches/2.0.x/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java?rev=927266&r1=927265&r2=927266&view=diff
==============================================================================
--- openjpa/branches/2.0.x/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java (original)
+++ openjpa/branches/2.0.x/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java Thu Mar 25 03:55:39 2010
@@ -2276,8 +2276,14 @@ public class BrokerImpl
         }
         if (opt)
             return new OptimisticException(t);
+        
+        Object failedObject = null;
+        if (t[0] instanceof OpenJPAException){
+        	failedObject = ((OpenJPAException)t[0]).getFailedObject();
+        }
+        
         return new StoreException(_loc.get("rolled-back")).
-            setNestedThrowables(t).setFatal(true);
+            setNestedThrowables(t).setFatal(true).setFailedObject(failedObject);
     }
 
     /**

Modified: openjpa/branches/2.0.x/openjpa-lib/src/main/java/org/apache/openjpa/lib/jdbc/LoggingConnectionDecorator.java
URL: http://svn.apache.org/viewvc/openjpa/branches/2.0.x/openjpa-lib/src/main/java/org/apache/openjpa/lib/jdbc/LoggingConnectionDecorator.java?rev=927266&r1=927265&r2=927266&view=diff
==============================================================================
--- openjpa/branches/2.0.x/openjpa-lib/src/main/java/org/apache/openjpa/lib/jdbc/LoggingConnectionDecorator.java (original)
+++ openjpa/branches/2.0.x/openjpa-lib/src/main/java/org/apache/openjpa/lib/jdbc/LoggingConnectionDecorator.java Thu Mar 25 03:55:39 2010
@@ -242,7 +242,7 @@ public class LoggingConnectionDecorator 
     }
 
     private SQLException wrap(SQLException sqle, Statement stmnt, int indexOfFailedBatchObject) {
-        return wrap(sqle, stmnt, null, -1);
+        return wrap(sqle, stmnt, null, indexOfFailedBatchObject);
     }
 
     /**
@@ -1114,7 +1114,7 @@ public class LoggingConnectionDecorator 
                     // we are tracking parameters, then set the current
                     // parameter set to be the index of the failed
                     // statement so that the ReportingSQLException will
-                    // show the correct param
+                    // show the correct param(s)
                     if (se instanceof BatchUpdateException
                         && _paramBatch != null && shouldTrackParameters()) {
                         int[] count = ((BatchUpdateException) se).
@@ -1161,7 +1161,6 @@ public class LoggingConnectionDecorator 
                     throw err;
                 } finally {
                     logTime(start);
-                    clearLogParameters(true);
                     handleSQLErrors(LoggingPreparedStatement.this, err);
                 }
             }
@@ -1409,16 +1408,12 @@ public class LoggingConnectionDecorator 
             }
 
             private void clearLogParameters(boolean batch) {
-                //Made !batch...we only want to clear if
-                //we are NOT using batching.  If we clear now,
-                //the _params will not be displayed in the resultant
-                //exception message.  But when should we 'clear' them???
-                if (!batch){                    
-                    if (_params != null)
-                        _params.clear();
-                    
-                    if (_paramBatch != null)
-                        _paramBatch.clear();                
+                if (_params != null) {
+                    _params.clear();
+                }
+
+                if (batch && _paramBatch != null) {
+                    _paramBatch.clear();
                 }
             }
 
@@ -1605,6 +1600,9 @@ public class LoggingConnectionDecorator 
             private final String _sql;
             private List<String> _params = null;
             private List<List<String>> _paramBatch = null;
+            //When batching is used, this variable contains the index into the last
+            //successfully executed batched statement.
+            int batchedRowsBaseIndex = 0;            
 
             public LoggingCallableStatement(CallableStatement stmt, String sql) 
                 throws SQLException {
@@ -1709,11 +1707,29 @@ public class LoggingConnectionDecorator 
             }
 
             public int[] executeBatch() throws SQLException {
+                int indexOfFirstFailedObject = -1;
+
                 logBatchSQL(this);
                 long start = System.currentTimeMillis();
                 SQLException err = null;                
                 try {
-                    return super.executeBatch();
+                    int[] toReturn = super.executeBatch();
+                    //executeBatch is called any time the number of batched statements
+                    //is equal to, or less than, batchLimit.  In the 'catch' block below, 
+                    //the logic seeks to find an index based on the current executeBatch 
+                    //results.  This is fine when executeBatch is only called once, but 
+                    //if executeBatch is called many times, the _paramsBatch will continue 
+                    //to grow, as such, to index into _paramsBatch, we need to take into 
+                    //account the number of times executeBatch is called in order to 
+                    //correctly index into _paramsBatch.  To that end, each time executeBatch 
+                    //is called, lets get the size of _paramBatch.  This will effectively 
+                    //tell us the index of the last successfully executed batch statement.  
+                    //If an exception is caused, then we know that _paramBatch.size was 
+                    //the index of the LAST row to successfully execute.
+                    if (_paramBatch != null){
+                        batchedRowsBaseIndex = _paramBatch.size();                        
+                    }
+                    return toReturn; 
                 } catch (SQLException se) {
                     // if the exception is a BatchUpdateException, and
                     // we are tracking parameters, then set the current
@@ -1726,12 +1742,11 @@ public class LoggingConnectionDecorator 
                             getUpdateCounts();
                         if (count != null && count.length <= _paramBatch.size())
                         {
-                            int index = -1;
                             for (int i = 0; i < count.length; i++) {
                                 // -3 is Statement.STATEMENT_FAILED, but is
                                 // only available in JDK 1.4+
                                 if (count[i] == Statement.EXECUTE_FAILED) {
-                                    index = i;
+                                    indexOfFirstFailedObject = i;
                                     break;
                                 }
                             }
@@ -1739,19 +1754,35 @@ public class LoggingConnectionDecorator 
                             // no -3 element: it may be that the server stopped
                             // processing, so the size of the count will be
                             // the index
-                            if (index == -1)
-                                index = count.length + 1;
+                            //See the Javadoc for 'getUpdateCounts'; a provider 
+                            //may stop processing when the first failure occurs, 
+                            //as such, it may only return 'UpdateCounts' for the 
+                            //first few which pass.  As such, the failed 
+                            //index is 'count.length', NOT count.length+1.  That
+                            //is, if the provider ONLY returns the first few that 
+                            //passes (i.e. say an array of [1,1] is returned) then
+                            //length is 2, and since _paramBatch starts at 0, we 
+                            //don't want to use length+1 as that will give us the 
+                            //wrong index.
+                            if (indexOfFirstFailedObject == -1){
+                                indexOfFirstFailedObject = count.length;
+                            }
+                            
+                            //Finally, whatever the index is at this point, add batchedRowsBaseIndex
+                            //to it to get the final index.  Recall, we need to start our index from the
+                            //last batch which successfully executed.
+                            indexOfFirstFailedObject += batchedRowsBaseIndex;
 
                             // set the current params to the saved values
-                            if (index < _paramBatch.size())
-                                _params = (List<String>) _paramBatch.get(index);
+                            if (indexOfFirstFailedObject < _paramBatch.size()){
+                                _params = (List<String>) _paramBatch.get(indexOfFirstFailedObject);
+                            }
                         }
                     }
-                    err = wrap(se, LoggingCallableStatement.this);
+                    err = wrap(se, LoggingCallableStatement.this, indexOfFirstFailedObject);
                     throw err;
                 } finally {
                     logTime(start);
-                    clearLogParameters(true);
                     handleSQLErrors(LoggingCallableStatement.this, err);
                 }
             }

Added: openjpa/branches/2.0.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/Ent1.java
URL: http://svn.apache.org/viewvc/openjpa/branches/2.0.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/Ent1.java?rev=927266&view=auto
==============================================================================
--- openjpa/branches/2.0.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/Ent1.java (added)
+++ openjpa/branches/2.0.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/Ent1.java Thu Mar 25 03:55:39 2010
@@ -0,0 +1,73 @@
+/*
+ * 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.batch.exception;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+@Entity
+public class Ent1 {
+    // primary key:
+    @Id
+    private int pk;    
+    public int getPk() {return pk;}
+    public void setPk(int pk) {this.pk = pk;}
+    
+    private String name;
+    public String getName(){return name;}
+    public void setName(String str){
+        name = str;
+    }
+    
+    public Ent1() {}    
+    public Ent1(int pk, String str) {this.pk = pk;name=str;}     
+    
+    public String toString(){
+        return "Ent1 [pk = " + pk + ", " + name +"]";
+    }
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        result = prime * result + pk;
+        return result;
+    }
+    
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        Ent1 other = (Ent1) obj;
+        if (name == null) {
+            if (other.name != null)
+                return false;
+        } else if (!name.equals(other.name))
+            return false;
+        if (pk != other.pk)
+            return false;
+        return true;
+    }
+}
+
+

Propchange: openjpa/branches/2.0.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/Ent1.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: openjpa/branches/2.0.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/TestBatchLimitException.java
URL: http://svn.apache.org/viewvc/openjpa/branches/2.0.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/TestBatchLimitException.java?rev=927266&view=auto
==============================================================================
--- openjpa/branches/2.0.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/TestBatchLimitException.java (added)
+++ openjpa/branches/2.0.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/TestBatchLimitException.java Thu Mar 25 03:55:39 2010
@@ -0,0 +1,324 @@
+/*
+ * 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.batch.exception;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+
+import org.apache.openjpa.persistence.test.PersistenceTestCase;
+import org.apache.openjpa.util.ExceptionInfo;
+
+//This test was created for OPENJPA-1550.  In this issue the user was
+//not able to get the 'failed object' (the object causing the failure) when
+//batch limit was -1 or a value greater than 1.  Also, they found that the
+//'params' listed in the prepared statement were missing.  This test will set
+//various batch limits and verify that with the fix to 1550, the correct
+//'failed object' and prepared statement is returned. 
+public class TestBatchLimitException extends PersistenceTestCase {
+
+    static Ent1 expectedFailedObject;
+    final String expectedFailureMsg =
+        "INSERT INTO Ent1 (pk, name) VALUES (?, ?) [params=(int) 200, (String) twohundred]";
+
+    public EntityManagerFactory newEmf(String batchLimit) {
+        EntityManagerFactory emf =
+            createEMF(Ent1.class, 
+                "openjpa.jdbc.SynchronizeMappings", 
+                "buildSchema(ForeignKeys=true)",
+                "openjpa.jdbc.DBDictionary", batchLimit, 
+                CLEAR_TABLES);
+
+        assertNotNull("Unable to create EntityManagerFactory", emf);
+        return emf;
+    }
+
+    public void setUp() {
+        expectedFailedObject = null;
+    }
+
+    // Test that we get the correct 'failed object' when we have a batchLimt
+    // of X and Y rows, where X>Y. A duplicate row will be inserted
+    // sometime within the Y rows. This will verify that we get the right
+    // 'failed object' and message.
+    public void testExceptionInFirstBatch() throws Throwable {
+        EntityManagerFactory emf = newEmf("batchLimit=-1");
+        EntityManager em = emf.createEntityManager();
+
+        em.getTransaction().begin();
+        em.persist(new Ent1(1, "one"));
+        expectedFailedObject = new Ent1(200, "twohundred");
+        em.persist(expectedFailedObject);
+        em.persist(new Ent1(5, "five"));
+        em.getTransaction().commit();
+        em.close();
+
+        EntityManager em2 = emf.createEntityManager();
+
+        em2.getTransaction().begin();
+        em2.persist(new Ent1(0, "zero"));
+        em2.persist(new Ent1(2, "two"));
+        em2.persist(new Ent1(200, "twohundred"));
+        em2.persist(new Ent1(3, "three"));
+        em2.persist(new Ent1(1, "one"));
+        em2.persist(new Ent1(5, "five"));
+        
+        try {
+            em2.getTransaction().commit();
+        } catch (Throwable excp) {
+            verifyExDetails(excp);
+        }
+        finally {
+            if (em2.getTransaction().isActive()) {
+                em2.getTransaction().rollback();
+            }
+            em2.close();
+            emf.close();
+        }
+    }
+
+    // Test that we get the correct 'failed object' when there is only one
+    // row in the batch. The 'batching' logic executes a different
+    // statement when only one row is to be updated/inserted.
+    public void testExceptionSingleBatchedRow() throws Throwable {
+        EntityManagerFactory emf = newEmf("batchLimit=-1");
+        EntityManager em = emf.createEntityManager();
+
+        em.getTransaction().begin();
+        expectedFailedObject = new Ent1(200, "twohundred");
+        em.persist(expectedFailedObject);
+        em.getTransaction().commit();
+        em.close();
+
+        EntityManager em2 = emf.createEntityManager();
+
+        em2.getTransaction().begin();
+        em2.persist(new Ent1(200, "twohundred"));
+        
+        try {
+            em2.getTransaction().commit();
+        } catch (Throwable excp) {
+            verifyExDetails(excp);
+        }
+        finally {
+            if (em2.getTransaction().isActive()) {
+                em2.getTransaction().rollback();
+            }
+            em2.close();
+            emf.close();
+        }
+    }
+
+    // Test that we get the correct 'failed object' and message when we
+    // have a batchLimt of X and Y rows, where Y>X. In this case, the
+    // batch is executed every time the batchLimt is hit. A duplicate
+    // row will be inserted sometime after X (X+1, i.e right at the
+    // boundary of the batch) to verify that we get the right
+    // 'failed object' and msg no matter which batch a duplicate is
+    // contained in. This test is important because as part of the
+    // fix to OPENJPA-1510 we had to add extra logic to keep track
+    // of which batch the 'failed object' was in, along with the
+    // index into that batch.
+    public void testExceptionInSecondBatch() throws Throwable {
+        EntityManagerFactory emf = newEmf("batchLimit=9");
+        EntityManager em = emf.createEntityManager();
+
+        em.getTransaction().begin();
+        expectedFailedObject = new Ent1(200, "twohundred");
+        em.persist(expectedFailedObject);
+        em.getTransaction().commit();
+        em.close();
+
+        EntityManager em2 = emf.createEntityManager();
+
+        em2.getTransaction().begin();
+
+        // Put 9 objects/rows into the batch
+        for (int i = 0; i < 9; i++) {
+            em2.persist(new Ent1(i, "name" + i));
+        }
+
+        // Put the duplicate object/row as the first element in the second batch.
+        em2.persist(new Ent1(200, "twohundred"));
+        
+        try {
+            em2.getTransaction().commit();
+        } catch (Throwable excp) {
+            verifyExDetails(excp);
+        }
+        finally {
+            if (em2.getTransaction().isActive()) {
+                em2.getTransaction().rollback();
+            }
+            em2.close();
+            emf.close();
+        }
+    }
+
+    // Same as testRowsGreaterThanBatchLimit_boundaryCase, but the object to cause the failure
+    // is in the middle of the second batch. testExceptioninSecondBatch puts
+    // the failing object as the first element in the second batch, this test puts
+    // it somewhere in the middle of the third batch. Again, we want to make sure our
+    // indexing into the batch containing the 'failed object' is correct.
+    public void testExceptionInThirdBatch() throws Throwable {
+        EntityManagerFactory emf = newEmf("batchLimit=9");
+        EntityManager em = emf.createEntityManager();
+
+        em.getTransaction().begin();
+        expectedFailedObject = new Ent1(200, "twohundred");
+        em.persist(expectedFailedObject);
+        em.getTransaction().commit();
+        em.close();
+
+        EntityManager em2 = emf.createEntityManager();
+
+        em2.getTransaction().begin();
+
+        // Persist 21 objects/rows....as such we will have two 'full'
+        // batches (9*2=18) and 3 (21-18=3) objects/rows in the 3rd batch.
+        for (int i = 0; i < 22; i++) {
+            em2.persist(new Ent1(i, "name" + i));
+        }
+
+        // Put the duplicate row in the 3rd batch.
+        em2.persist(new Ent1(200, "twohundred"));
+
+        // Put a few more objects into the batch.
+        for (int i = 22; i < 40; i++) {
+            em2.persist(new Ent1(i, "name" + i));
+        }
+
+        try {
+            em2.getTransaction().commit();
+        } catch (Throwable excp) {
+            verifyExDetails(excp);
+        }
+        finally {
+            if (em2.getTransaction().isActive()) {
+                em2.getTransaction().rollback();
+            }
+            em2.close();
+            emf.close();
+        }
+    }
+
+    // Similar to the previous two tests, but lets run the test with a large
+    // batch with a failure, and then commit, then run large batches
+    // again with failures again.....just want to make sure things are not in
+    // some way 're-used' between the two commits as far as the indexes go.
+    public void testSecondExceptionHasRightIndex() throws Throwable {
+        testExceptionInThirdBatch();
+
+        EntityManagerFactory emf = newEmf("batchLimit=9");
+        EntityManager em = emf.createEntityManager();
+
+        em.getTransaction().begin();
+
+        for (int i = 40; i < 55; i++) {
+            em.persist(new Ent1(i, "name" + i));
+        }
+
+        em.persist(new Ent1(200, "twohundred"));
+
+        for (int i = 55; i < 65; i++) {
+            em.persist(new Ent1(i, "name" + i));
+        }
+
+        try {
+            em.getTransaction().commit();
+        } catch (Throwable excp) {
+            verifyExDetails(excp);
+        }
+        finally {
+            if (em.getTransaction().isActive()) {
+                em.getTransaction().rollback();
+            }
+            em.close();
+            emf.close();
+        }
+    }
+
+    public void testExceptionWithMultipleCommits() throws Throwable {
+        EntityManagerFactory emf = newEmf("batchLimit=-1");
+        EntityManager em = emf.createEntityManager();
+
+        em.getTransaction().begin();
+        em.persist(new Ent1(1, "one"));
+        expectedFailedObject = new Ent1(200, "twohundred");
+        em.persist(expectedFailedObject);
+        em.persist(new Ent1(5, "five"));
+        em.getTransaction().commit();
+        em.close();
+
+        EntityManager em2 = emf.createEntityManager();
+        em2.getTransaction().begin();
+        em2.persist(new Ent1(0, "zero"));
+        em2.persist(new Ent1(2, "two"));
+        em2.persist(new Ent1(3, "three"));
+        em2.getTransaction().commit();
+
+        em2.getTransaction().begin();
+        em2.persist(new Ent1(6, "six"));
+        em2.persist(new Ent1(200, "twohundred"));
+        em2.persist(new Ent1(7, "seven"));
+        
+        try {
+            em2.getTransaction().commit();
+        } catch (Throwable excp) {
+            verifyExDetails(excp);
+        }
+        finally {
+            if (em2.getTransaction().isActive()) {
+                em2.getTransaction().rollback();
+            }
+            em2.close();
+            emf.close();
+        }
+    }
+
+    // Verify that the resultant exception contains the correct 'failed object'
+    // and exception message.
+    public void verifyExDetails(Throwable excp) throws Throwable {
+        // The exception should contain the 'failed object'
+        verifyFailedObject(excp);
+        // The second cause should contain the message which shows the failing prepared statement.
+        Throwable cause = excp.getCause().getCause();
+        verifyExMsg(cause.getMessage());
+    }
+
+    public void verifyFailedObject(Throwable excp) throws Throwable {
+        if (excp instanceof ExceptionInfo) {
+            ExceptionInfo e = (ExceptionInfo) excp;
+
+            Ent1 failedObject = (Ent1) e.getFailedObject();
+
+            assertNotNull("Failed object was null.", failedObject);
+            assertEquals(expectedFailedObject, failedObject);
+        }
+        else {
+            throw excp;
+        }
+    }
+
+    public void verifyExMsg(String msg) {
+        assertNotNull("Exception message was null.", msg);
+        assertTrue("Did not see expected text in message. Expected <" + expectedFailureMsg + "> but was " + msg, msg
+            .contains(expectedFailureMsg));
+    }
+}
+

Propchange: openjpa/branches/2.0.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/TestBatchLimitException.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: openjpa/branches/2.0.x/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java
URL: http://svn.apache.org/viewvc/openjpa/branches/2.0.x/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java?rev=927266&r1=927265&r2=927266&view=diff
==============================================================================
--- openjpa/branches/2.0.x/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java (original)
+++ openjpa/branches/2.0.x/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java Thu Mar 25 03:55:39 2010
@@ -81,6 +81,7 @@ import org.apache.openjpa.persistence.cr
 import org.apache.openjpa.persistence.criteria.OpenJPACriteriaBuilder;
 import org.apache.openjpa.persistence.criteria.OpenJPACriteriaQuery;
 import org.apache.openjpa.persistence.validation.ValidationUtils;
+import org.apache.openjpa.util.ExceptionInfo;
 import org.apache.openjpa.util.Exceptions;
 import org.apache.openjpa.util.ImplHelper;
 import org.apache.openjpa.util.RuntimeExceptionTranslator;
@@ -569,8 +570,15 @@ public class EntityManagerImpl
             // normal exception translator, since the spec says they
             // should be thrown whenever the commit fails for any reason at
             // all, wheras the exception translator handles exceptions that
-            // are caused for specific reasons
-            throw new RollbackException(e);
+            // are caused for specific reasons            
+
+            // pass along the failed object if one is available.
+            Object failedObject = null;
+            if (e instanceof ExceptionInfo){
+            	failedObject = ((ExceptionInfo)e).getFailedObject();            	
+            }
+            
+            throw new RollbackException(e).setFailedObject(failedObject);
         }
     }
 

Modified: openjpa/branches/2.0.x/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/RollbackException.java
URL: http://svn.apache.org/viewvc/openjpa/branches/2.0.x/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/RollbackException.java?rev=927266&r1=927265&r2=927266&view=diff
==============================================================================
--- openjpa/branches/2.0.x/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/RollbackException.java (original)
+++ openjpa/branches/2.0.x/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/RollbackException.java Thu Mar 25 03:55:39 2010
@@ -38,6 +38,8 @@ import org.apache.openjpa.util.Exception
 public class RollbackException
     extends javax.persistence.RollbackException
     implements Serializable, ExceptionInfo {
+	
+    private transient Object _failed = null;
 
     private transient Throwable[] _nested;
 
@@ -67,7 +69,12 @@ public class RollbackException
     }
 
     public Object getFailedObject() {
-        return null;
+        return _failed;
+    }
+
+    public RollbackException setFailedObject(Object failed) {
+        _failed = failed;
+        return this;
     }
 
     public String toString() {

Modified: openjpa/branches/2.0.x/openjpa-project/CHANGES.txt
URL: http://svn.apache.org/viewvc/openjpa/branches/2.0.x/openjpa-project/CHANGES.txt?rev=927266&r1=927265&r2=927266&view=diff
==============================================================================
--- openjpa/branches/2.0.x/openjpa-project/CHANGES.txt (original)
+++ openjpa/branches/2.0.x/openjpa-project/CHANGES.txt Thu Mar 25 03:55:39 2010
@@ -201,6 +201,7 @@ Bug
     * [OPENJPA-1544] - Remove WebSphere version number from org/apache/ee/localizer.properties
     * [OPENJPA-1546] - OpenJPA doesn't work as internal JPA inside web applicaion in JBoss AS
     * [OPENJPA-1547] - NOT IN with MEMBER OF returns syntax error
+    * [OPENJPA-1550] - When batchLimit=-1 or >1 and an exception is caused, the params and failedObject are missing from the resultant exception.
     * [OPENJPA-1556] - Exception thrown on first use of @Strategy in @Embeddable classes
     * [OPENJPA-1558] - Many side of a MxO relationship contains null reference if One side is loaded first.
     * [OPENJPA-1562] - EntityManager:Refresh on Removed entity does not trigger IllegalArgumentException

Modified: openjpa/branches/2.0.x/openjpa-project/RELEASE-NOTES.html
URL: http://svn.apache.org/viewvc/openjpa/branches/2.0.x/openjpa-project/RELEASE-NOTES.html?rev=927266&r1=927265&r2=927266&view=diff
==============================================================================
--- openjpa/branches/2.0.x/openjpa-project/RELEASE-NOTES.html (original)
+++ openjpa/branches/2.0.x/openjpa-project/RELEASE-NOTES.html Thu Mar 25 03:55:39 2010
@@ -296,6 +296,8 @@ in each release of OpenJPA.</P>
 </li>
 <li>[<a href='https://issues.apache.org/jira/browse/OPENJPA-1547'>OPENJPA-1547</a>] -         NOT IN with MEMBER OF returns syntax error
 </li>
+<li>[<a href='https://issues.apache.org/jira/browse/OPENJPA-1550'>OPENJPA-1550</a>] -         When batchLimit=-1 or >1 and an exception is caused, the params and failedObject are missing from the resultant exception.
+</li>
 <li>[<a href='https://issues.apache.org/jira/browse/OPENJPA-1556'>OPENJPA-1556</a>] -         Exception thrown on first use of @Strategy in @Embeddable classes
 </li>
 <li>[<a href='https://issues.apache.org/jira/browse/OPENJPA-1558'>OPENJPA-1558</a>] -         Many side of a MxO relationship contains null reference if One side is loaded first.