在多线程环境的集成测试 class 上方使用 @Transactional 注释的问题

Issue using @Transactional annotation above integration test class for multithreading environment

当我 运行 对在新线程中调用 JPA 存储库的代码进行集成测试时,我得到的数据是在启动 PostgreSQLContainer 期间填充的,但我无法接收数据从上面的脚本 class test( @Sql(scripts ="data.sql").
但是当我删除测试上方的 @Transactional 注释时,我可以从测试和测试容器的 SQL 脚本中获取数据。
我的问题是可以在不删除 @Transactional 注释的情况下从测试脚本获取多线程环境中的数据吗? 谢谢您的回答!

应用程序堆栈: Spring 启动 2.1v+ 测试容器 PostgreSQL 1.10.3v+ JUnit 4.12v

数据库测试容器配置

@TestConfiguration
public class DatabaseTestConfig {

    private static JdbcDatabaseContainer PSQL;

    static {

        PSQL = (PostgreSQLContainer) new PostgreSQLContainer("mdillon/postgis:9.4").withUsername("test")
                .withPassword("test")
                .withDatabaseName("test");
        PSQL.start();

        Arrays.asList("main_data.sql")
                .forEach(DatabaseTestConfig::restoreDump);

        /*
           set db properties
        */

    }

    public void restoreDump(String fileName){
         /*
           insert sql data 
           PSQL.copyFileToContainer(fileName)...
        */
    }

}

基础集成测试class

@RunWith(SpringRunner.class)
@SpringBootTest(classes = { DatabaseTestConfig.class, ProjectApplication.class })
@ActiveProfiles("test-int")
@AutoConfigureMockMvc
@Sql(scripts = "classpath:extra_data.sql") // insert some extra data for all integration tests
public abstract class AbstractIntTest {
    @Autowired
    protected MockMvc mockMvc;

调用服务的集成测试

@Transactional
public class SomeIntegrationTest extends AbstractIntTest {


    @Before
    public void setUp() throws IOException {
      //...
    }

    @Test
    public void callServiceTest() throws Exception {
      //mockMvc.perform(post(ENDPOINT_URL)
    }

简化逻辑的服务

@Service
@AllArgsConstructor
public class SomeService {
    private final SomeJpaReporistory repo;
    private final ExecutorService executor;

    @Override
    @Transactional
    public SomeData call(){
        return CompletableFuture.supplyAsync(() -> {
            return repo.findAll(); 
        }, executor).exceptionally(e -> {
            throw new BadRequestException(e.getMessage());
        });
    }

当您进行事务性测试时,extra_data.sql 中的 SQL 查询将在事务中执行。该事务绑定到特定线程,并在执行测试方法之前开始,并在测试方法完成后回滚:

  1. 开始交易
  2. 执行extra_data.sql
  3. 调用测试方法
  4. 回滚事务

在第 3 步中,由于您的服务使用 supplyAsync,您将在单独的线程上调用 repo.findAll()。由于事务绑定到特定线程,此 findAll() 调用不是执行 extra_data.sql 的事务的一部分。为了能够读取 extra_data.sql 添加的数据,它必须能够读取未提交的更改并执行脏读。 Postgres 不支持读取未提交隔离级别,因此这是不可能的。

您需要重新审视如何使用测试数据填充数据库或在测试中使用事务。或许您可以以与 main_data.sql 相同的方式将 extra_data.sql 应用于数据库,以便它在执行任何测试之前和事务开始之前始终就位。

我是这样解决这个问题的:

@Test
@Transactional
@Sql(scripts = "/db/extra_data.sql",
config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
void test() {
    // extra_data.sql are executed before this test is run.
}