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