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/16 20:46:15 UTC

svn commit: r923949 - in /openjpa/branches/1.2.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: Tue Mar 16 19:46:15 2010
New Revision: 923949

URL: http://svn.apache.org/viewvc?rev=923949&view=rev
Log:
OPENJPA-1550:
When batchLimit=-1 or >1 and an exception is caused, the params and failedObject are missing from the resultant exception.
Submitted By: Heath Thomann

Added:
    openjpa/branches/1.2.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/
    openjpa/branches/1.2.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/
    openjpa/branches/1.2.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/Ent1.java   (with props)
    openjpa/branches/1.2.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/TestBatchLimitException.java   (with props)
Modified:
    openjpa/branches/1.2.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/BatchingPreparedStatementManagerImpl.java
    openjpa/branches/1.2.x/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java
    openjpa/branches/1.2.x/openjpa-lib/src/main/java/org/apache/openjpa/lib/jdbc/LoggingConnectionDecorator.java
    openjpa/branches/1.2.x/openjpa-lib/src/main/java/org/apache/openjpa/lib/jdbc/ReportingSQLException.java

Modified: openjpa/branches/1.2.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/BatchingPreparedStatementManagerImpl.java
URL: http://svn.apache.org/viewvc/openjpa/branches/1.2.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/BatchingPreparedStatementManagerImpl.java?rev=923949&r1=923948&r2=923949&view=diff
==============================================================================
--- openjpa/branches/1.2.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/BatchingPreparedStatementManagerImpl.java (original)
+++ openjpa/branches/1.2.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/BatchingPreparedStatementManagerImpl.java Tue Mar 16 19:46:15 2010
@@ -33,6 +33,7 @@ import org.apache.openjpa.jdbc.sql.Row;
 import org.apache.openjpa.jdbc.sql.RowImpl;
 import org.apache.openjpa.jdbc.sql.SQLExceptions;
 import org.apache.openjpa.kernel.OpenJPAStateManager;
+import org.apache.openjpa.lib.jdbc.ReportingSQLException;
 import org.apache.openjpa.lib.log.Log;
 import org.apache.openjpa.lib.util.Localizer;
 import org.apache.openjpa.util.OptimisticException;
@@ -189,15 +190,47 @@ public class BatchingPreparedStatementMa
                     checkUpdateCount(rtn, batchedRowsBaseIndex, ps);
                 }
             } catch (SQLException se) {
+                //If we look at PreparedStatementManagerImpl.flushAndUpdate (which is the 'non-batch' code path
+                //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.
                 SQLException sqex = se.getNextException();
-                if (sqex == null)
+                if (sqex == null){
                     sqex = se;
-                throw SQLExceptions.getStore(sqex, ps, _dict);
+                }
+                
+                if (se instanceof ReportingSQLException){
+                  int index = ((ReportingSQLException) se).getIndexOfFirstFailedObject();
+
+                  //if we have only batched one statement, the index should be 0.  As can be seen above,
+                  //if 'batchSize == 1' a different path is taken (the 'single row' path), and if that row
+                  //fails, we know that the index is 0 since there is only one row.
+                  if (batchSize == 1){
+                      index = 0;
+                  }
+                  
+                  //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(se, ps, _dict);
+                  }
+                  else{
+                      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) {
                     try {
+                        ps.clearParameters();
                         ps.close();
                     } catch (SQLException sqex) {
                         throw SQLExceptions.getStore(sqex, ps, _dict);

Modified: openjpa/branches/1.2.x/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java
URL: http://svn.apache.org/viewvc/openjpa/branches/1.2.x/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java?rev=923949&r1=923948&r2=923949&view=diff
==============================================================================
--- openjpa/branches/1.2.x/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java (original)
+++ openjpa/branches/1.2.x/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java Tue Mar 16 19:46:15 2010
@@ -2184,8 +2184,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/1.2.x/openjpa-lib/src/main/java/org/apache/openjpa/lib/jdbc/LoggingConnectionDecorator.java
URL: http://svn.apache.org/viewvc/openjpa/branches/1.2.x/openjpa-lib/src/main/java/org/apache/openjpa/lib/jdbc/LoggingConnectionDecorator.java?rev=923949&r1=923948&r2=923949&view=diff
==============================================================================
--- openjpa/branches/1.2.x/openjpa-lib/src/main/java/org/apache/openjpa/lib/jdbc/LoggingConnectionDecorator.java (original)
+++ openjpa/branches/1.2.x/openjpa-lib/src/main/java/org/apache/openjpa/lib/jdbc/LoggingConnectionDecorator.java Tue Mar 16 19:46:15 2010
@@ -66,7 +66,7 @@ public class LoggingConnectionDecorator 
     private static final int WARN_THROW = 5;
     private static final int WARN_HANDLE = 6;
     private static final String[] WARNING_ACTIONS = new String[7];
-
+    
     static {
         WARNING_ACTIONS[WARN_IGNORE] = "ignore";
         WARNING_ACTIONS[WARN_LOG_TRACE] = "trace";
@@ -183,23 +183,47 @@ public class LoggingConnectionDecorator 
         return new LoggingConnection(conn);
     }
 
+    private SQLException wrap(SQLException sqle, Statement stmnt) {
+        return wrap(sqle,stmnt,-1);        
+    }
+
     /**
      * Include SQL in exception.
      */
-    private SQLException wrap(SQLException sqle, Statement stmnt) {
+    private SQLException wrap(SQLException sqle, Statement stmnt, int indexOfFailedBatchObject) {
+        ReportingSQLException toReturn = null;
+        
         if (sqle instanceof ReportingSQLException)
-            return (ReportingSQLException) sqle;
-        return new ReportingSQLException(sqle, stmnt);
+            toReturn =  (ReportingSQLException) sqle;
+        else        
+            toReturn = new ReportingSQLException(sqle, stmnt);
+        
+        toReturn.setIndexOfFirstFailedObject(indexOfFailedBatchObject);
+        return toReturn;
     }
 
+    private SQLException wrap(SQLException sqle, String sql) {
+        return wrap(sqle, sql,-1);
+    }
+    
     /**
      * Include SQL in exception.
      */
-    private SQLException wrap(SQLException sqle, String sql) {
-        if (sqle instanceof ReportingSQLException)
-            return (ReportingSQLException) sqle;
-        return new ReportingSQLException(sqle, sql);
+    private SQLException wrap(SQLException sqle, String sql, int indexOfFailedBatchObject) {
+        ReportingSQLException toReturn = null;
+        
+        if (sqle instanceof ReportingSQLException){
+            toReturn = (ReportingSQLException) sqle;
+        }
+        else{
+            toReturn = new ReportingSQLException(sqle, sql);
+        }
+        
+        toReturn.setIndexOfFirstFailedObject(indexOfFailedBatchObject);
+        
+        return toReturn;
     }
+   
 
     /**
      * Interface that allows customization of what to do when
@@ -787,6 +811,9 @@ public class LoggingConnectionDecorator 
             private final String _sql;
             private List _params = null;
             private List _paramBatch = null;
+            //When batching is used, this variable contains the index into the last
+            //successfully executed batched statement.
+            int batchedRowsBaseIndex = 0;            
 
             public LoggingPreparedStatement(PreparedStatement stmnt, String sql)
                 throws SQLException {
@@ -872,28 +899,45 @@ public class LoggingConnectionDecorator 
             }
 
             public int[] executeBatch() throws SQLException {
+                int indexOfFirstFailedObject = -1;
+
                 logBatchSQL(this);
                 long start = System.currentTimeMillis();
-                try {
-                    return super.executeBatch();
+                try {                    
+                    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
                     // 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).
                             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] == -3) {
-                                    index = i;
+                                    indexOfFirstFailedObject = i;
                                     break;
                                 }
                             }
@@ -901,18 +945,34 @@ 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){
+                                //   index = count.length + 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) _paramBatch.get(index);
+                            if (indexOfFirstFailedObject < _paramBatch.size())
+                                _params = (List) _paramBatch.get(indexOfFirstFailedObject);
                         }
                     }
-                    throw wrap(se, LoggingPreparedStatement.this);
+                    throw wrap(se, LoggingPreparedStatement.this, indexOfFirstFailedObject);
                 } finally {
                     logTime(start);
-                    clearLogParameters(true);
                     handleSQLWarning(LoggingPreparedStatement.this);
                 }
             }
@@ -1153,10 +1213,13 @@ public class LoggingConnectionDecorator 
             }
 
             private void clearLogParameters(boolean batch) {
-                if (_params != null)
+                if (_params != null) {
                     _params.clear();
-                if (batch && _paramBatch != null)
+                }
+
+                if (batch && _paramBatch != null) {
                     _paramBatch.clear();
+                }
             }
 
             private boolean shouldTrackParameters() {

Modified: openjpa/branches/1.2.x/openjpa-lib/src/main/java/org/apache/openjpa/lib/jdbc/ReportingSQLException.java
URL: http://svn.apache.org/viewvc/openjpa/branches/1.2.x/openjpa-lib/src/main/java/org/apache/openjpa/lib/jdbc/ReportingSQLException.java?rev=923949&r1=923948&r2=923949&view=diff
==============================================================================
--- openjpa/branches/1.2.x/openjpa-lib/src/main/java/org/apache/openjpa/lib/jdbc/ReportingSQLException.java (original)
+++ openjpa/branches/1.2.x/openjpa-lib/src/main/java/org/apache/openjpa/lib/jdbc/ReportingSQLException.java Tue Mar 16 19:46:15 2010
@@ -32,6 +32,9 @@ public class ReportingSQLException exten
 
     private final transient Statement _stmnt;
     private final SQLException _sqle;
+    //When batching is used, and an object/row in the batch causes an exception, this
+    //variable will hold the index of the first failing object.
+    private int indexOfFirstFailedObject=-1;
 
     public ReportingSQLException(SQLException sqle, Statement stmnt,
         String sql) {
@@ -69,7 +72,15 @@ public class ReportingSQLException exten
     public Statement getStatement() {
         return _stmnt;
     }
+    
+    public int getIndexOfFirstFailedObject(){
+        return indexOfFirstFailedObject;
+    }
 
+    public void setIndexOfFirstFailedObject(int index){    
+        indexOfFirstFailedObject=index;
+    }
+    
     private static String getExceptionMessage(SQLException sqle,
         Statement stmnt, String sql) {
         try {

Added: openjpa/branches/1.2.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/Ent1.java
URL: http://svn.apache.org/viewvc/openjpa/branches/1.2.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/Ent1.java?rev=923949&view=auto
==============================================================================
--- openjpa/branches/1.2.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/Ent1.java (added)
+++ openjpa/branches/1.2.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/Ent1.java Tue Mar 16 19:46:15 2010
@@ -0,0 +1,87 @@
+/*
+ * 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/1.2.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/Ent1.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: openjpa/branches/1.2.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/TestBatchLimitException.java
URL: http://svn.apache.org/viewvc/openjpa/branches/1.2.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/TestBatchLimitException.java?rev=923949&view=auto
==============================================================================
--- openjpa/branches/1.2.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/TestBatchLimitException.java (added)
+++ openjpa/branches/1.2.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/TestBatchLimitException.java Tue Mar 16 19:46:15 2010
@@ -0,0 +1,314 @@
+/*
+ * 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 cause should contain the 'failed object'
+        Throwable cause = excp.getCause();
+        verifyFailedObject(cause);
+        // The second cause should contain the message which shows the failing prepared statement.
+        cause = cause.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/1.2.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/batch/exception/TestBatchLimitException.java
------------------------------------------------------------------------------
    svn:eol-style = native