Testing Active Objects : java.lang.IllegalStateException: There are two concurrently open transactions!

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;
    }

First solution:

  • 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);
        }
    }
}
3 Likes