Affects: Spring Boot 3.3.5
I asked on stackoverflow and also found similar questions there:
https://stackoverflow.com/questions/78022351/spring-boot-jpa-transaction-is-committing-but-record-is-not-getting-inserted https://stackoverflow.com/questions/78863344/frequent-rollback-on-connection-close-in-spring-boot-with-hikaricp-and-mariadb
The problem:
Simple table:
CREATE TABLE table_master
(
id INTEGER NOT NULL,
name VARCHAR(200),
CONSTRAINT t_master_pk PRIMARY KEY (id)
);
Test:
@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Slf4j
class JdbcTest {
private DataSource fbDataSource;
@Autowired
private PlatformTransactionManager transactionManager;
@BeforeEach
public void setUp() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:firebirdsql://localhost:3055//opt/fb/db/DBGDB");
config.setUsername("user");
config.setPassword("passwd");
config.setAutoCommit(false);
fbDataSource = new HikariDataSource(config);
}
private static final String insertQuery = "INSERT INTO table_master(id, name) VALUES(?, ?)";
private static final String cntQuery = "SELECT COUNT(*) FROM table_master";
@Test
public void testFbLoad() throws SQLException {
JdbcTemplate fbJdbcTemplate = new JdbcTemplate(fbDataSource);
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRES_NEW);
def.setIsolationLevel(DefaultTransactionDefinition.ISOLATION_READ_COMMITTED);
TransactionStatus status = transactionManager.getTransaction(def);
if (fbJdbcTemplate.update(insertQuery, 1, "Row") == 0) {
throw new RuntimeException("Cannot insert row");
}
transactionManager.commit(status);
Integer cnt = fbJdbcTemplate.queryForObject(cntQuery, Integer.class);
log.info("Rows in table_master: {}", cnt);
}
Log:
2024-11-30T04:10:31.534+02:00 DEBUG 15966 --- [ Test worker] o.h.e.t.internal.TransactionImpl : On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false
2024-11-30T04:10:31.535+02:00 DEBUG 15966 --- [ Test worker] o.h.e.t.internal.TransactionImpl : begin
2024-11-30T04:10:31.536+02:00 DEBUG 15966 --- [ Test worker] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@ea34dee]
2024-11-30T04:10:31.536+02:00 DEBUG 15966 --- [ Test worker] o.s.jdbc.core.JdbcTemplate : Executing prepared SQL update
2024-11-30T04:10:31.537+02:00 DEBUG 15966 --- [ Test worker] o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement [INSERT INTO table_master(id, name) VALUES(?, ?)]
2024-11-30T04:10:31.537+02:00 DEBUG 15966 --- [ Test worker] o.s.jdbc.datasource.DataSourceUtils : Fetching JDBC Connection from DataSource
2024-11-30T04:10:31.544+02:00 DEBUG 15966 --- [ Test worker] o.f.gds.ng.wire.AbstractWireOperations : readStatusVector arg:isc_arg_gds int: 0
2024-11-30T04:10:31.553+02:00 DEBUG 15966 --- [ Test worker] o.f.gds.ng.wire.AbstractWireOperations : readStatusVector arg:isc_arg_gds int: 0
2024-11-30T04:10:31.553+02:00 DEBUG 15966 --- [ Test worker] o.f.gds.ng.wire.AbstractWireOperations : readStatusVector arg:isc_arg_gds int: 0
2024-11-30T04:10:31.567+02:00 DEBUG 15966 --- [ Test worker] o.f.gds.ng.wire.AbstractWireOperations : readStatusVector arg:isc_arg_gds int: 0
2024-11-30T04:10:31.568+02:00 DEBUG 15966 --- [ Test worker] o.f.gds.ng.wire.AbstractWireOperations : readStatusVector arg:isc_arg_gds int: 0
2024-11-30T04:10:31.570+02:00 DEBUG 15966 --- [ Test worker] o.f.jaybird.xca.FBManagedConnection : End called: Xid[413118226]
2024-11-30T04:10:31.573+02:00 DEBUG 15966 --- [ Test worker] o.f.gds.ng.wire.AbstractWireOperations : readStatusVector arg:isc_arg_gds int: 0
2024-11-30T04:10:31.573+02:00 DEBUG 15966 --- [ Test worker] o.f.gds.ng.wire.AbstractWireOperations : readStatusVector arg:isc_arg_gds int: 0
2024-11-30T04:10:31.574+02:00 DEBUG 15966 --- [ Test worker] com.zaxxer.hikari.pool.ProxyConnection : HikariPool-1 - Executed rollback on connection org.firebirdsql.jdbc.FBConnection@394e21b9 due to dirty commit state on close().
2024-11-30T04:10:31.574+02:00 DEBUG 15966 --- [ Test worker] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit
2024-11-30T04:10:31.574+02:00 DEBUG 15966 --- [ Test worker] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(865867007<open>)]
2024-11-30T04:10:31.574+02:00 DEBUG 15966 --- [ Test worker] o.h.e.t.internal.TransactionImpl : committing
2024-11-30T04:10:31.575+02:00 DEBUG 15966 --- [ Test worker] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(865867007<open>)] after transaction
Problem:
No data inserted due to:
2024-11-30T04:10:31.574+02:00 DEBUG 15966 --- [ Test worker] com.zaxxer.hikari.pool.ProxyConnection : HikariPool-1 - Executed rollback on connection org.firebirdsql.jdbc.FBConnection@394e21b9 due to dirty commit state on close().
Reason:
JdbcTemplate
closes connection in the update method. Hikari rolls the transaction back due to the connection' dirty state then. Then JpaTransactionManager
tries to commit it but it was already rolled back. So, the example from the Spring docs is not working.
Stack:
doCloseConnection:406, DataSourceUtils (org.springframework.jdbc.datasource)
doReleaseConnection:393, DataSourceUtils (org.springframework.jdbc.datasource)
releaseConnection:360, DataSourceUtils (org.springframework.jdbc.datasource)
beforeCompletion:525, DataSourceUtils$ConnectionSynchronization (org.springframework.jdbc.datasource)
triggerBeforeCompletion:108, TransactionSynchronizationUtils (org.springframework.transaction.support)
triggerBeforeCompletion:996, AbstractPlatformTransactionManager (org.springframework.transaction.support)
processCommit:776, AbstractPlatformTransactionManager (org.springframework.transaction.support)
commit:758, AbstractPlatformTransactionManager (org.springframework.transaction.support)
testFbLoad:105, JdbcTest (com.freewayfleet.dbreplicator)
Comment From: quaff
Can not be reproduced by H2.
import static org.assertj.core.api.Assertions.assertThat;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
@org.springframework.boot.test.autoconfigure.jdbc.JdbcTest
@ContextConfiguration(classes = JdbcTest.class)
@Sql(statements = "CREATE TABLE table_master(id INTEGER NOT NULL,name VARCHAR(200),CONSTRAINT t_master_pk PRIMARY KEY (id))")
class JdbcTest {
@Autowired
private DataSource dataSource;
@Autowired
private PlatformTransactionManager transactionManager;
private static final String insertQuery = "INSERT INTO table_master(id, name) VALUES(?, ?)";
private static final String cntQuery = "SELECT COUNT(*) FROM table_master";
@Test
public void test() throws SQLException {
JdbcTemplate fbJdbcTemplate = new JdbcTemplate(dataSource);
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRES_NEW);
def.setIsolationLevel(DefaultTransactionDefinition.ISOLATION_READ_COMMITTED);
TransactionStatus status = transactionManager.getTransaction(def);
if (fbJdbcTemplate.update(insertQuery, 1, "Row") == 0) {
throw new RuntimeException("Cannot insert row");
}
transactionManager.commit(status);
Integer cnt = fbJdbcTemplate.queryForObject(cntQuery, Integer.class);
assertThat(cnt).isEqualTo(1);
}
}
Comment From: BbIKTOP
Looks like it fails with custom datasource
Comment From: mdeinum
As mentioned in the stackoverflow issue, the datasource isnt even known to Spring and the PlatformTransactionManager
in use is tied to another DataSource
so won't manage the transactions. As the auto-commit
is set to false you are basically running without a transaction, no transaction no persisting.
Comment From: snicoll
@BbIKTOP please do not cross post. Especially since you've accepted the StackOverflow answer which left the duplicate thread you've created here open for no reason.