简体   繁体   中英

Inserts not committed until after transaction completes using PlatformTransactionManager

I need to ensure that inserts are committed during the execution of a stored procedure. Using Spring Boot Starter Data JPA 1.5, this was the default behavior for a JpaTransactionManager . With Spring Boot 2.x, inserts are not committed until the stored procedure completes execution.

So, calling this stored procedure using Spring Boot 2.x transaction mgmt, will not insert after 1 minute:

CREATE procedure [dbo].[test_insert]
as
begin
    insert into myTempTable(col1)
    values ("1");

    WAITFOR DELAY '00:01';
end
GO

But calling it using Spring Boot 1.5 transaction mgmt, will insert immediately which is what I need.

Here's the code. Transaction Manager:

@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(entityManagerFactory);
    return transactionManager;
} 

Repo:

public interface TestRepository extends Repository<String, Long> {

    @Procedure("test_insert")
    void testInsert();

}

Test case:

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional(transactionManager="transactionManager")
public class InsertRepositoryTest {

    @Autowired
    private TestRepository testRepo;

    @Test
    public void testInsert() throws SQLException {
        testRepo.testInsert();
    }   

}

I'm using SQL Server 2016 and its default transaction level is set to READ COMMITTED.

How can I make sure that inserts in a stored procedure are committed immediately during execution?

Update

After a bit more research, I found that with Spring's transaction manager I'm in implicit transaction mode. But when I don't use it, I'm in autocommit. In other words, select @@OPTIONS & 2 returns 2 with Spring's transaction manager and 0 without it. So, I think that explains why I'm seeing differences on when the insert commits. If that's right, then I just need to know how to set autocommit. The default mode must have changed either with Spring or the SQL Server jdbc driver (I was using 4.2 and now using 8.2.0.jre8).

Update 2

Why is it important that some other process can see the insert into mytempTable before the stored procedure completes? Because in the production stored procedure, it does the insert, calls off into some service (that I believe selects data from that row and inserts data into another table), and then the sp waitfor data to appear in the table inserted by the service. So, in the case that the service can't see insert, the stored procedure hangs waiting for the service to insert in the other table.

I think the solution is to not use an explicit transaction and just let SQL Server default to auto commit. Unfortunately, that also means no rollback if something goes wrong in the stored procedure. But given the stored procedure and the service are third party components that I can't change, I suppose that is my only option. Still wondering why the behavior was different between Spring Boot 1.5 and 2.x or the JDBC driver.

Update 3

It turns out that there is no difference in the behavior between Spring Boot 1.5 and 2.x or the JDBC driver versions. That is, the inserts never occur until after the transaction completes. The reason that I observed the difference in behavior has something to do with the fact that I was testing using Spring Boot 1.5 in an older project and that older project is a composite project with two data source and two transaction managers unlike my Spring Boot 2.x project. Not sure why that or what would make insert occur immediately in the old project but it does.

At any rate, looking at SQL Server logs, I can confirm that regardless of Spring / JDBC version, it inserts immediately when auto-commit mode is on.

If executing a stored procedure follows the 'transactional-write-behind' pattern of standard entity operations then the normal behavior is for SQL statements to be queued until such time as the transaction completes.

Spring Data repositories are transactional by default so even without the explicit @Transactional(transactionManager="transactionManager") in your test a transaction is in operation.

https://vladmihalcea.com/a-beginners-guide-to-jpahibernate-flush-strategies/ (assuming you are using Hibernate here but probably similar concepts in other JPA implementations).

Try this to trigger an immediate flush:

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional(transactionManager="transactionManager")
public class InsertRepositoryTest {

    @Autowired
    private TestRepository testRepo;

    @PersistenceContext 
    primate EntityManager em;

    @Test
    public void testInsert() throws SQLException {
        testRepo.testInsert();
        em.flush(); //write all buffered SQL statements
    }   
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM