blob: 26a23589222b081327884d6c187bb947525a5826 [file] [log] [blame]
/*
* 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.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.jdbc.sql.OracleDictionary;
import org.apache.openjpa.jdbc.sql.PostgresDictionary;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
import org.apache.openjpa.persistence.test.AbstractPersistenceTestCase;
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 AbstractPersistenceTestCase {
static Ent1 expectedFailedObject;
static Ent1 expectedFailedObjectOracle;
static boolean isOracle = false;
static boolean isPostgres = false;
final String expectedFailureMsg =
"INSERT INTO Ent1 (pk, name) VALUES (?, ?) [params=(int) 200, (String) twohundred]";
final String expectedFailureMsg18 =
"INSERT INTO Ent1 (pk, name) VALUES (?, ?) [params=(int) 18, (String) name18]";
String expectedFailureMsgOracle = expectedFailureMsg18;
public EntityManagerFactory newEmf(String batchLimit) {
OpenJPAEntityManagerFactorySPI emf =
createEMF(Ent1.class,
"openjpa.jdbc.SynchronizeMappings",
"buildSchema(ForeignKeys=true)",
"openjpa.jdbc.DBDictionary", batchLimit,
"openjpa.ConnectionFactoryProperties", "PrintParameters=true",
CLEAR_TABLES);
assertNotNull("Unable to create EntityManagerFactory", emf);
JDBCConfiguration conf = (JDBCConfiguration) emf.getConfiguration();
DBDictionary dict = conf.getDBDictionaryInstance();
isOracle = dict instanceof OracleDictionary;
isPostgres = dict instanceof PostgresDictionary;
return emf;
}
@Override
public void setUp() {
expectedFailedObject = null;
expectedFailedObjectOracle = 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();
// special case, due to how Oracle returns all statements in the batch
expectedFailedObjectOracle = new Ent1(18, "name18");
expectedFailureMsgOracle = expectedFailureMsg18;
em2.persist(expectedFailedObjectOracle);
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();
closeEMF(emf);
}
}
// 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");
expectedFailedObjectOracle = expectedFailedObject;
expectedFailureMsgOracle = expectedFailureMsg;
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();
closeEMF(emf);
}
}
// 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");
expectedFailedObjectOracle = expectedFailedObject;
expectedFailureMsgOracle = expectedFailureMsg;
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();
closeEMF(emf);
}
}
// 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 {
final int batchLimit=9;
EntityManagerFactory emf = newEmf("batchLimit="+batchLimit);
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.
int i=0;
for (; i < 2*batchLimit; i++) {
em2.persist(new Ent1(i, "name" + i));
}
// manually create third batch, due to how Oracle returns all statements in the batch
expectedFailedObjectOracle = new Ent1(i, "name" + i++);
expectedFailureMsgOracle = expectedFailureMsg18;
em2.persist(expectedFailedObjectOracle); // 18
em2.persist(new Ent1(i, "name" + i++)); // 19
em2.persist(new Ent1(i, "name" + i++)); // 20
em2.persist(new Ent1(i, "name" + i++)); // 21
// Put the duplicate row in the 3rd batch.
em2.persist(new Ent1(200, "twohundred"));
// Put a few more objects into the batch.
for (i = 22; i < 4*batchLimit; 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();
closeEMF(emf);
}
}
// 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 {
final int batchLimit=9;
testExceptionInThirdBatch();
EntityManagerFactory emf = newEmf("batchLimit=9");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
for (int i = 4*batchLimit; i < 5*batchLimit; i++) {
em.persist(new Ent1(i, "name" + i));
}
// manually capture start of batch, due to how Oracle returns all statements in the batch
expectedFailedObjectOracle = new Ent1(200, "twohundred");
expectedFailureMsgOracle = expectedFailureMsg;
em.persist(expectedFailedObjectOracle);
for (int i = 5*batchLimit; i < 7*batchLimit; 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();
closeEMF(emf);
}
}
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(4, "four"));
em2.persist(new Ent1(0, "zero"));
em2.persist(new Ent1(2, "two"));
em2.persist(new Ent1(3, "three"));
em2.getTransaction().commit();
em2.getTransaction().begin();
// special case, due to how Oracle returns all statements in the batch
expectedFailedObjectOracle = new Ent1(18, "name18");
expectedFailureMsgOracle = expectedFailureMsg18;
em2.persist(expectedFailedObjectOracle);
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();
closeEMF(emf);
}
}
// 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);
if (!isOracle && !isPostgres) {
assertEquals(expectedFailedObject, failedObject);
} else {
// special case, as Oracle returns all statements in the batch
assertEquals(expectedFailedObjectOracle, failedObject);
}
}
else {
throw excp;
}
}
public void verifyExMsg(String msg) {
assertNotNull("Exception message was null.", msg);
if (!isOracle && !isPostgres) {
assertTrue("Did not see expected text in message. Expected <" + expectedFailureMsg + "> but was " +
msg, msg.contains(expectedFailureMsg));
} else {
// special case, as Oracle returns all statements in the batch
assertTrue("Did not see expected text in message. Expected <" + expectedFailureMsgOracle + "> but was " +
msg, msg.contains(expectedFailureMsgOracle));
}
}
}