Spring 使用@Sql 的引导测试锁定表和存储库,服务无法读取

Spring Boot test with @Sql locks tables and repositores and services cannot read

我正在使用 SQL Server 2016 作为生产和开发数据库(因此没有 h2),具有 Spring Boot 2 后端和 Angular 7 前端。如果我 运行 应用程序正常,一切都会按预期工作。 对于集成测试 (JUnit5),我想使用 Selenium,因此应用程序实际上需要在端口上 运行 并且可以通过浏览器访问。我还需要在每次测试前准备一些数据(因为我不能使用生产数据)。为了实现这两个目标,我计划使用 spring's @Sql annotation,它允许我在测试前执行任何 .sql 文件(在我的例子中,我插入了我想要操作的数据)。 Spring 测试后回滚所有内容,因此它应该运行良好。但是,当我插入数据时,测试启动的事务锁定了数据库表,而另一个 services/repositories 应用程序使用(例如查询数据)被阻止。

示例:我在 @Sql 注释中链接的文件中插入了一名员工,然后 Selenium 启动了浏览器,我导航到包含员工的列表。此时,员工列表将无法工作(使用 REST 端点和 EmployeeRepository 提供服务),因为它被测试的事务阻止了。

我也可以手动确认此锁定,而测试 运行,我无法使用 SQL Server Management Studio 执行查询(等待并在我结束测试时完成)。

我可以使用 @Sql 或任何其他数据准备工具进行测试,让应用程序正常工作,同时还能够在测试后回滚更改吗?

测试class:

@SpringBootTest(classes = ...App.class)
@ActiveProfiles(profiles = "test")
@ExtendWith(SpringExtension.class)
@Transactional
public class SeleniumExampleIT {
    @LocalServerPort
    protected int port;

    protected WebDriver driver;
    protected NgWebDriver ngWebDriver;

    @BeforeAll
    public static void setupClass() {
        WebDriverManager.chromedriver().setup();
    }

    @BeforeEach
    public void setupTest() {
        var chromeDriver = new ChromeDriver();
        driver = chromeDriver;
        ngWebDriver = new NgWebDriver(chromeDriver);
        ngWebDriver.waitForAngularRequestsToFinish();
    }

    @AfterEach
    public void teardown() {
        if (driver != null) {
            driver.quit();
        }
    }

    @Test
    @Sql({"classpath:sql/test.sql"})
    void listEmployeesTest() {
        //...starting selenium
        // navigating, waiting ..etc
    }
}

EmployeeRepository:

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
    @Query("SELECT DISTINCT e FROM Employee e " +
        "LEFT JOIN FETCH e.settings")
    List<Employee> findAll();
    @Query("SELECT DISTINCT e FROM Employee e " +
        "LEFT JOIN FETCH e.settings " +
        "WHERE e.id = :id")
    Optional<Employee> findById(@Param("id") Long id);
}

编辑:用 @Transactional 注释 JpaRepository 方法没有解决问题(如 所述)。

Edit2.: 根据Spring's docs

If your test is @Transactional, it rolls back the transaction at the end of each test method by default. However, as using this arrangement with either RANDOM_PORT or DEFINED_PORT implicitly provides a real servlet environment, the HTTP client and server run in separate threads and, thus, in separate transactions. Any transaction initiated on the server does not roll back in this case.

我能找到的唯一解决方案是不使用 @Transactional 测试。我创建了一个单独的数据库,仅用于测试运行。我在每次测试之前使用如下脚本清理数据库:

-- CHECK IF DATABASE HAS TESTING ALLOWED
IF (OBJECT_ID('dbo.tests_allowed') IS NULL)
BEGIN
    RAISERROR('Testing is NOT allowed in the selected database! Please, check carefully, before removing all data! If you want to allow testing, create table dbo.`tests_allowed`.', 16, 1)
END;

-- DISABLE CHECKS
ALTER TABLE dbo.<table-names> NOCHECK CONSTRAINT ALL;

-- TRUNCATE TABLES
DELETE FROM dbo.<table-names>;

-- ENABLE CHECKS
ALTER TABLE dbo.<table-names> WITH CHECK CHECK CONSTRAINT ALL;

截断 运行 后,我用数据插入脚本准备数据库,像这样执行它:

@Test
@DisplayName("Checking settings....")
@Sql(value = {"classpath:sql/truncate-tables.sql", "classpath:sql/example-dataset-1.sql"},
    config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
public void check...Test() {}

使用隔离模式,Sql 文件将立即提交,我可以在测试期间读取数据。

这边:

  • 数据库总是在测试前清理,我不需要担心测试期间的脏数据
  • 如果测试失败,我可以检查失败状态下的数据库
  • 在截断脚本之前进行检查可防止在生产或开发环境中进行 运行 测试(我手动将 tests_allowed table 添加到测试数据库)。
  • 无需担心 Hibernate 不提交数据或 t运行sactions locking/deadlocking