【问题标题】:Inserts not committed until after transaction completes using PlatformTransactionManager直到使用 PlatformTransactionManager 完成事务后才提交插入
【发布时间】:2020-05-29 15:48:39
【问题描述】:

我需要确保在执行存储过程期间提交插入。使用 Spring Boot Starter Data JPA 1.5,这是 JpaTransactionManager 的默认行为。使用 Spring Boot 2.x,在存储过程完成执行之前不会提交插入。

所以,使用 Spring Boot 2.x 事务管理调用这个存储过程,1 分钟后不会插入:

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

    WAITFOR DELAY '00:01';
end
GO

但是使用 Spring Boot 1.5 事务管理调用它,会立即插入,这是我需要的。

这是代码。事务管理器:

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

回购:

public interface TestRepository extends Repository<String, Long> {

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

}

测试用例:

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

    @Autowired
    private TestRepository testRepo;

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

}

我使用的是 SQL Server 2016,其默认事务级别设置为 READ COMMITTED。

如何确保存储过程中的插入在执行期间立即提交?

更新

经过更多研究,我发现使用 Spring 的事务管理器,我处于隐式事务模式。但是当我不使用它时,我处于自动提交状态。换句话说,select @@OPTIONS &amp; 2 返回带有 Spring 事务管理器的 2 和没有它的 0。所以,我认为这解释了为什么我在插入提交时看到了差异。如果那是正确的,那么我只需要知道如何设置自动提交。必须使用 Spring 或 SQL Server jdbc 驱动程序更改默认模式(我使用的是 4.2,现在使用的是 8.2.0.jre8)。

更新 2

为什么其他进程可以在存储过程完成之前看到插入到 mytempTable 中的内容很重要?因为在生产存储过程中,它执行插入,调用某些服务(我相信从该行中选择数据并将数据插入另一个表),然后 sp waitfor 数据出现在由插入的表中服务。因此,在服务看不到插入的情况下,存储过程会挂起,等待服务插入到另一个表中。

我认为解决方案是不使用显式事务,而让 SQL Server 默认自动提交。不幸的是,如果存储过程出现问题,这也意味着不会回滚。但是鉴于存储过程和服务是我无法更改的第三方组件,我想这是我唯一的选择。仍然想知道为什么 Spring Boot 1.5 和 2.x 或 JDBC 驱动程序之间的行为不同。

更新 3

事实证明,Spring Boot 1.5 和 2.x 或 JDBC 驱动程序版本之间的行为没有区别。也就是说,插入永远不会发生,直到事务完成之后。我观察到行为差异的原因与我在旧项目中使用 Spring Boot 1.5 进行测试的事实有关,并且旧项目是一个复合项目,具有两个数据源和两个事务管理器,与我的 Spring Boot 2 不同。 x 项目。不知道为什么会在旧项目中立即发生插入,但确实如此。

无论如何,查看 SQL Server 日志,我可以确认无论 Spring / JDBC 版本如何,它都会在自动提交模式打开时立即插入。

【问题讨论】:

  • 从文档开始 - 不是 Java 开发人员,但看起来您的存储库正在将 sql 连接包装在事务中。 SQK Server 将跟踪此事务。您要么点击那一侧,要么更改 SP 以读取未提交的更改 - 交易被回滚的危险。这将是对 SP 的更改(查看那里的选项 - 你可以说 READUNCOMMITTED)。
  • 谢谢汤姆汤姆。我为 OP 提供了一些更新。我认为问题在于服务(存储过程调用并且我在waitfor 期间通过我的SSMS 选择查询进行模拟)看不到插入。我无法控制该服务。所以,我不能告诉它读取未提交。因此,我想我将在没有显式 txn 的情况下调用 sp,并让 SQL Server 默认使用自动提交模式(它将在 sp 中的每个语句之后提交)。这意味着没有回滚,但我想我别无选择。仍然想知道 Spring / JDBC 驱动程序发生了什么变化。

标签: sql-server spring-boot transactions spring-data-jpa spring-data


【解决方案1】:

如果执行存储过程遵循标准实体操作的“事务后写”模式,则正常行为是 SQL 语句排队,直到事务完成。

Spring Data 存储库默认是事务性的,因此即使在您的测试中没有显式的 @Transactional(transactionManager="transactionManager"),事务也在运行。

https://vladmihalcea.com/a-beginners-guide-to-jpahibernate-flush-strategies/(假设您在这里使用的是 Hibernate,但在其他 JPA 实现中可能有类似的概念)。

试试这个来触发立即刷新:

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

【讨论】:

  • 这是不正确的。如果没有定义事务,则每个命令都是一个单独的事务。我已经反复使用它来删除大量数据,方法是组合一个循环并删除存储过程中事务之外的前 X 百万行。然后,您描述的行为分解为“针对每个命令”的语义,而问题显然与此无关。
  • 如果没有定义事务??测试正在在事务中运行:@Transactional(transactionManager="transactionManager")???当提问者在更新 3 中注意到此行为时,为什么要投反对票 也就是说,插入永远不会发生,直到事务完成后看起来您的存储库正在将 sql 连接包装在事务中 Spring Data 存储库默认情况下是事务性的,因此即使在测试结束后,SP 也会被包装在事务中。
  • 默认情况下所有 Spring Data 存储库都是事务性的吗?这个documentation 似乎表明只有SimpleJpaRepository 是事务性的。但是我的存储库正在扩展Repository。如果我指示从不传播,则插入的 txn 会立即发生@Transactional(propagation = Propagation.NEVER, transactionManager="apxTransactionManager")
猜你喜欢
  • 2015-01-14
  • 2019-09-22
  • 1970-01-01
  • 1970-01-01
  • 2016-02-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多