mirror of
https://github.com/apache/openjpa.git
synced 2025-02-20 17:05:15 +00:00
OPENJPA-1550:
Set failedObject on RollbackException. Submitted By: Heath Thomann git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@927267 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
162b11b542
commit
666ec6c6de
@ -192,12 +192,11 @@ public class BatchingPreparedStatementManagerImpl extends
|
||||
//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 BatchingPreparedStatementManagerImpl extends
|
||||
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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -242,7 +242,7 @@ public class LoggingConnectionDecorator implements ConnectionDecorator {
|
||||
}
|
||||
|
||||
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 implements ConnectionDecorator {
|
||||
// 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 implements ConnectionDecorator {
|
||||
throw err;
|
||||
} finally {
|
||||
logTime(start);
|
||||
clearLogParameters(true);
|
||||
handleSQLErrors(LoggingPreparedStatement.this, err);
|
||||
}
|
||||
}
|
||||
@ -1409,16 +1408,12 @@ public class LoggingConnectionDecorator implements ConnectionDecorator {
|
||||
}
|
||||
|
||||
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 implements ConnectionDecorator {
|
||||
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 implements ConnectionDecorator {
|
||||
}
|
||||
|
||||
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 implements ConnectionDecorator {
|
||||
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 implements ConnectionDecorator {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +81,7 @@ import org.apache.openjpa.persistence.criteria.CriteriaBuilderImpl;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,6 +38,8 @@ import org.apache.openjpa.util.Exceptions;
|
||||
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() {
|
||||
|
@ -206,6 +206,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
|
||||
|
@ -301,6 +301,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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user