升级至Spring-tx-5.3+后事务配置调整及TDS协议错误解决
问题描述
将Spring Boot从2.3.12.RELEASE升级至2.4.0及以上版本(对应spring-tx从5.2.15升级至5.3+)后,SQL Server JDBC驱动报出如下错误:
c.m.sqlserver.jdbc.internals.TDS.Reader : TDSReader@30(ConnectionID:1 ClientConnectionId:ff3ee928-1a1c-4d97-b4b7-f13d87f4d35c) got unexpected value in TDS response at offset:7992
c.m.s.jdbc.internals.SQLServerException : *** SQLException:ConnectionID:1 ClientConnectionId:ff3ee928-1a1c-4d97-b4b7-f13d87f4d35c com.microsoft.sqlserver.jdbc.SQLServerException: The TDS protocol stream is not valid. The TDS protocol stream is not valid.
背景信息
代码调用两个存储过程:一个用于在数据库建立应用会话,另一个获取varbinary(max)类型数据。在Spring Boot 2.4.0+版本中,读取该二进制数据的字节时触发上述错误。测试类代码如下:
@RunWith(SpringRunner.class) @SpringBootTest(classes=CarServiceApplication.class) @Transactional(propagation = Propagation.NEVER, transactionManager="carTransactionManager") @Slf4j public class HoldingRepositoryTest { @Autowired private SessionRepository sessionRepository; @Autowired private CarRepository carRepository; @Autowired private AppProperties appLogin; @Test public void test() throws SQLException { sessionRepository.getSession(appLogin.getUser(), appLogin.getPassword()); LocalDate july5Of2016 = LocalDate.of(2016, 7, 5); Blob reportData = carRepository.getReportData("VIN1234", july5Of2016); byte[] reportDataBytes = reportData.getBytes(1, (int) reportData.length()); //error occurs here assertThat(reportData, notNullValue()); } }
事务管理器配置如下:
@Configuration @EnableJpaRepositories(basePackages={"com.example.car.repository"}, entityManagerFactoryRef="carEntityManagerFactory", transactionManagerRef = "carTransactionManager") @EnableJpaAuditing public class CarDatabaseConfiguration { @Bean public LocalContainerEntityManagerFactoryBean carEntityManagerFactory( EntityManagerFactoryBuilder builder, DataSource dataSource) { return builder .dataSource(dataSource) .packages("com.example.car.domain") .persistenceUnit("car") .build(); } @Bean public PlatformTransactionManager carTransactionManager(EntityManagerFactory carEntityManagerFactory) { JpaTransactionManager transactionManager = new JpaTransactionManager(); transactionManager.setEntityManagerFactory(carEntityManagerFactory); return transactionManager; } }
已尝试操作
在Spring 2.3.12.RELEASE中移除@Transactional会出现相同的TDS错误,但升级至2.4.0+版本后,该注解似乎被忽略。
核心问题
- 如何修改事务配置以避免该错误,恢复至Spring Boot2.3.12.RELEASE版本的运行状态?
- 为何将
getReportData调用移至服务层方法并添加@Transactional(propagation = Propagation.NEVER, transactionManager="carTransactionManager")(或Propagation.NOT_SUPPORTED)后问题解决? - 为何Spring Boot2.4.0+版本中,测试类的
@Transactional注解会被忽略,而服务层方法的该注解可正常生效?
解答
1. 修复事务配置以恢复旧版本运行状态
有两种可行方案:
- 方案一:调整测试类事务注解
给测试类的@Transactional添加@Rollback(false),同时确保propagation = Propagation.NEVER配置正确。Spring Boot 2.4+对测试类事务的处理逻辑有调整,默认的回滚机制会干扰连接状态,导致TDS协议解析异常。 - 方案二:全局配置测试事务行为
在application-test.properties中添加配置:
或者直接禁用测试类的默认事务管理,确保调用存储过程时没有事务上下文干扰,避免连接被事务管理器复用或状态重置。spring.test.transaction.rollback-on-commit-failure=false
2. 服务层添加事务注解解决问题的原因
Propagation.NEVER和Propagation.NOT_SUPPORTED都会强制在无事务上下文的环境下执行代码:
Propagation.NEVER:如果当前存在事务则直接抛出异常,确保代码完全脱离事务;Propagation.NOT_SUPPORTED:如果当前存在事务则挂起它,执行完后再恢复。
建立应用会话的存储过程可能会修改连接的会话状态(比如设置特定的上下文参数),而事务上下文会复用连接或重置连接状态,导致后续读取varbinary(max)数据时,TDS协议流的状态不一致。将getReportData放到服务层并添加这两种传播级别的注解,会让该方法在独立的无事务连接中执行,避免了事务对连接状态的干扰,从而解决TDS解析错误。
3. Spring Boot2.4+测试类事务注解被忽略的原因
这是因为Spring Boot 2.4+对@SpringBootTest的事务处理逻辑做了优化:
- 旧版本(2.3.x)中,测试类的
@Transactional会直接覆盖默认的测试事务配置; - 新版本(2.4+)中,
@SpringBootTest默认启用了测试事务管理器的自动配置,会优先使用内置的测试事务逻辑,导致测试类上自定义的@Transactional(尤其是指定了自定义事务管理器和传播级别)被忽略。
而服务层的@Transactional不受此影响,因为它是由业务层的事务切面处理,和测试类的事务管理逻辑相互独立,所以能正常生效。
内容的提问来源于stack exchange,提问作者James

