在多线程环境的集成测试 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 查询将在事务中执行。该事务绑定到特定线程,并在执行测试方法之前开始,并在测试方法完成后回滚:
- 开始交易
- 执行
extra_data.sql
- 调用测试方法
- 回滚事务
在第 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.
}
当我 运行 对在新线程中调用 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 查询将在事务中执行。该事务绑定到特定线程,并在执行测试方法之前开始,并在测试方法完成后回滚:
- 开始交易
- 执行
extra_data.sql
- 调用测试方法
- 回滚事务
在第 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.
}