Hi Everyone,
I am trying to test active object accessor methods. I am getting an error
“java.lang.IllegalStateException: There are two concurrently open transactions!”
I am pretty sure that all the written tests are concurrent and I think the test runner class while trying to reset the database is doing something which is causing this error to occur. Because I am getting this error after the test case has run.
This is my DatabaseUpdater class ->
@Test
public void createEntry()throws Exception{
final AModel entry = aAccessor.createEntry(model);
System.out.println(entry.getApp());
assertNull(entry);
}
public static class AAccessorImplTestDatabaseUpdater implements DatabaseUpdater {
@Override
public void update(EntityManager em) throws Exception {
em.migrate(A.class);
ao = new TestActiveObjects(em);
ao.executeInTransaction(() -> {
final A a = ao.create(A.class);
a.setApp("GSP");
a.setComp("");
a.setIns("");
a.setPro("");
a.save();
return a;
});
ao.flushAll();
}
}
and this the method I am testing
@Nullable
public AModel createEntry(AModel model) throws NullArgumentException {
if (!validateEntry(model)) {
return null;
}
final AModel aModel = ao.executeInTransaction(() -> {
final A m = ao.create(A.class);
m.setApp(model.getApp());
m.setIns(model.getIns());
m.setPro(model.getPro());
m.setComp(model.getComp());
m.save();
m.setID(m.getID());
return model;
});
ao.flushAll(); //added to because of error
return aModel;
}
Annotate the test method with @net.java.ao.test.jdbc.NonTransactional.
Unfortunately, this means AO creates-and-drops the schema for each unit test instead of reverting the transaction (and it takes 12s to create-and-drop with Oracle and a few tables). Given a few hundred tests in a project, it’s easily 40 minutes of overhead in a build.
Second solution:
Override/replace TestActiveObjects with a custom implementation.
Why? Because it started a nested transaction, despite ActiveObjectsMethodRule already doing it,
The nested transactions made JDBC complain. We’ve replaced TestActiveObjects’ transaction with a no-op.
Here is the result:
/**
* Copy of {@link com.atlassian.activeobjects.test.TestActiveObjects} in version com.atlassian.activeobjects:activeobjects-test:3.2.10,
* the only difference is we don't wrap the transactions in one another, because:
* - ActiveObjectsJUnitRunner already uses ActiveObjectsTransactionMethodRule which starts the transaction in the 'before()';
* - If we do it, ActiveObjectsTransactionMethodRule will throw an exception while performing the rollback, saying there
* are two transactions open.
*/
public class CustomTestActiveObjects extends EntityManagedActiveObjects {
private static final Map<String, DatabaseType> DATABASE_PRODUCT_TO_TYPE_MAP = ImmutableMap.<String, DatabaseType>builder()
.put("H2", DatabaseType.H2)
.put("HSQL Database Engine", DatabaseType.HSQL)
.put("MySQL", DatabaseType.MYSQL)
.put("PostgreSQL", DatabaseType.POSTGRESQL)
.put("Oracle", DatabaseType.ORACLE)
.put("Microsoft SQL Server", DatabaseType.MS_SQL)
.put("DB2", DatabaseType.DB2)
.build();
public CustomTestActiveObjects(final EntityManager entityManager) {
super(entityManager, TransactionCallback::doInTransaction, findDatabaseType(entityManager));
}
private static DatabaseType findDatabaseType(EntityManager entityManager) {
try (Connection connection = entityManager.getProvider().getConnection()) {
String dbName = connection.getMetaData().getDatabaseProductName();
for (Entry<String, DatabaseType> entry : DATABASE_PRODUCT_TO_TYPE_MAP.entrySet()) {
if (dbName.startsWith(entry.getKey()))
return entry.getValue();
}
return DatabaseType.UNKNOWN;
} catch (SQLException sqle) {
throw new ActiveObjectsException(sqle);
}
}
}