1

I'm performing a transaction in JPA (EclipseLink) using a custom transaction isolation level, which I set on the underlying connection of the JPA EntityManager using this code:

// begin transaction
entityManager.getTransaction().begin();

// store the old isolation level
int isolationLevelOld = entityManager.unwrap(Connection.class).getTransactionIsolation();

// set the desired isolation level for this transaction
entityManager.unwrap(Connection.class).setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);

[...Queries...]

// commit transaction
entityManager.getTransaction().commit();

// reset isolation level to the old value (throws NullPointerException)
entityManager.unwrap(Connection.class).setTransactionIsolation(isolationLevelOld);

If I try to reset the isolation level to the old value after having committed the transaction, the underlying connection is null (entityManager.unwrap(Connection.class) returns null). I'm worried, if I just don't reset the isolation level, a connection with a bad isolation level gets leaked back to the pool.

What is the correct way of cleaning up after changing the isolation level? Should I maybe do it before calling commit()?

Roman C
  • 49,761
  • 33
  • 66
  • 176
Twilite
  • 873
  • 9
  • 22

2 Answers2

1

The java.sql.Connection gets returned to the pool in the call to entityManager.getTransaction().commit(); So resetting the isolation level afterwards is not possible and prevented by EclipseLink by returning a null connection.

Maintaining a reference to the Connection to circumvent this will likely leak a connection with altered settings, so I cannot accept your answer RomanC

I ended up creating two instances of EntityManagerFactory. One that creates default EntityManagers and one that creates EntityManagers with Connections with my desired transaction level using a SessionCustomizer:

public static class SessionCustomizer implements org.eclipse.persistence.config.SessionCustomizer {
    @Override
    public void customize(Session session) throws Exception {
        DatabaseLogin databaseLogin = (DatabaseLogin) session.getDatasourceLogin();
        databaseLogin.setTransactionIsolation(DatabaseLogin.TRANSACTION_SERIALIZABLE);
    }
}

private void init() {
    entityManagerFactoryRegular = Persistence.createEntityManagerFactory("MyPersitenceRegular");
    Map<String, String> props = new HashMap<>();
    props.put(PersistenceUnitProperties.SESSION_CUSTOMIZER, SessionCustomizer.class.getName());
    entityManagerFactoryTransactionSerializable = Persistence.createEntityManagerFactory("MyPersitenceTransactionSerializable", props);
}

See here Set Isolation level in eclipselink

I then use the EntityManagerFactory that provides whichever connection type I need. Caveat: Transactions cannot span EntityManagers from multiple EntityManagerFactories

Community
  • 1
  • 1
Twilite
  • 873
  • 9
  • 22
0

Try the following code

Connection conn = entityManager.unwrap(Connection.class);

conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);

[...Queries...]

// commit transaction
entityManager.getTransaction().commit();

conn.setTransactionIsolation(isolationLevelOld);
Roman C
  • 49,761
  • 33
  • 66
  • 176
  • I checked out the Eclipselink source code: The `java.sql.Connection` is encapsulated by a `DatabaseAccessor` which is returned to a pool on `entityManager.getTransaction().commit();`. I'm not sure about the life cycle of the pool and if it is local to the EntityManager, but if it is not, that means resetting the transaction isolation level after calling commit is too late, since the connection could have been reused elsewhere inbetween the two calls. – Twilite Jan 15 '16 at 14:34
  • Of course not, but if you acquire a connection from the pool it should be settled the default isolation, and when you start a new transaction this isolation will be used, in some cases you can change the isolation level of the current transaction, but I think it's not your case. When you commit a transaction you cannot use it anymore, even to change its isolation level, but if you close a connection it will not close a connection to the database but return a connection object to the pool, you can also test it before close to make sure the connection is valid. – Roman C Jan 15 '16 at 17:06