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.