I stumbled upon a problem while trying to write some tests again a h2 in-memory database using jooq. I do not know if the cause for the problem is my code, h2 or jooq.
I am using the transaction management from jooq (create.transaction(TransactionalRunnable ...)). When issuing a conflicting command (primary key violation), jooq tries to rollback the transaction, but without success. Jooq reports an error, leaves the transaction open and the row is still locked, resulting in further problems.
It is the second test that is not working. The first test is there just to demonstrate that the happy path is working.
The lines from the stack trace that seem to indicate the problem are:
Suppressed: org.jooq.exception.DataAccessException: Cannot rollback transaction
at org.jooq.impl.DefaultConnectionProvider.rollback(DefaultConnectionProvider.java:142)
at org.jooq.impl.DefaultTransactionProvider.rollback(DefaultTransactionProvider.java:220)
at org.jooq.impl.DefaultDSLContext.transactionResult(DefaultDSLContext.java:344)
... 30 more
Caused by: org.h2.jdbc.JdbcSQLException: Savepoint ist ungültig: "SYSTEM_SAVEPOINT_0"
Savepoint is invalid: "SYSTEM_SAVEPOINT_0"; SQL statement:
ROLLBACK TO SAVEPOINT SYSTEM_SAVEPOINT_0 [90063-190]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
at org.h2.message.DbException.get(DbException.java:179)
at org.h2.message.DbException.get(DbException.java:155)
at org.h2.engine.Session.rollbackToSavepoint(Session.java:946)
at org.h2.command.dml.TransactionCommand.update(TransactionCommand.java:59)
at org.h2.command.CommandContainer.update(CommandContainer.java:78)
at org.h2.command.Command.executeUpdate(Command.java:253)
at org.h2.jdbc.JdbcSavepoint.rollback(JdbcSavepoint.java:66)
at org.h2.jdbc.JdbcConnection.rollback(JdbcConnection.java:1037)
at org.jooq.impl.DefaultConnectionProvider.rollback(DefaultConnectionProvider.java:139)
... 32 more
Greetings
Steffen
package h2rollbacktest;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import org.jooq.SQLDialect;
import org.jooq.TransactionalRunnable;
import org.jooq.exception.DataAccessException;
import org.jooq.impl.DSL;
import org.jooq.impl.SQLDataType;
import org.junit.After;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class RollbackTest {
@Before
public void setup() {
withTransaction(configuration -> {
DSL.using(configuration)
.createTable("TEST")
.column("ID", SQLDataType.BIGINT.nullable(false))
.execute();
DSL.using(configuration)
.alterTable("TEST")
.add(DSL.constraint("PK").primaryKey("ID"))
.execute();
});
}
@After
public void cleanup() {
withTransaction(configuration -> {
DSL.using(configuration)
.dropTable("TEST")
.execute();
});
}
@Test
public void testDelete() {
withTransaction(configuration -> {
DSL.using(configuration)
.query("insert into TEST (ID) values (1)")
.execute();
});
withTransaction(configuration -> {
DSL.using(configuration)
.query("delete from TEST where ID=1")
.execute();
});
}
@Test
public void testDeleteAfterRollback() {
withTransaction(configuration -> {
DSL.using(configuration)
.query("insert into TEST (ID) values (1)")
.execute();
});
withTransaction(configuration -> {
DSL.using(configuration)
.query("insert into TEST (ID) values (1)")
.execute();
});
}
private Connection get() {
String url = "jdbc:h2:mem:runtime;DB_CLOSE_DELAY=-1";
Properties props = new Properties();
try {
Connection connection = DriverManager.getConnection(url, props);
connection.setAutoCommit(false);
return connection;
}
catch (SQLException e) {
throw new DataAccessException("no connection possible", e);
}
}
private void withTransaction(TransactionalRunnable transactionalCode) {
try (Connection connection = get()) {
DSL.using(connection, SQLDialect.H2).transaction(transactionalCode);
}
catch (SQLException e) {
throw new DataAccessException("no transaction possible", e);
}
}
}