[英]Rollback changes done to a MariaDB database by a spring test without @Transactional
我有一個執行類似操作的 Spring 服務:
@Service
public class MyService {
@Transactional(propagation = Propagation.NEVER)
public void doStuff(UUID id) {
// call an external service, via http for example, can be long
// update the database, with a transactionTemplate for example
}
}
Propagation.NEVER 表示在調用該方法時我們不能有活動事務,因為我們不想在等待來自外部服務的答復時阻塞與數據庫的連接。
現在,我怎樣才能正確測試這個然后回滾數據庫? 測試中的@Transactional 不起作用,由於 Propagation.NEVER 會出現異常。
@SpringBootTest
@Transactional
public class MyServiceTest {
@Autowired
private MyService myService;
public void testDoStuff() {
putMyTestDataInDb();
myService.doStuff(); // <- fails no transaction should be active
assertThat(myData).isTheWayIExpectedItToBe();
}
}
我可以刪除@Transactional,但我的數據庫在下一次測試中沒有處於一致狀態。
現在我的解決方案是在每次測試后在 @AfterEach junit 回調中截斷我的數據庫的所有表,但這有點笨拙並且當數據庫有多個表時會變得很慢。
我的問題來了:如何在不截斷/使用 @Transactional 的情況下回滾對數據庫所做的更改?
我正在測試的數據庫是帶有 testcontainers 的 mariadb,因此僅適用於 mariadb/mysql 的解決方案對我來說就足夠了。 但是更一般的東西會很棒!
(另一個我希望不能在測試中使用 @Transactional 的示例:有時我想測試事務邊界是否正確放入代碼中,並且不會在運行時遇到一些延遲加載異常,因為我在某處忘記了 @Transactional在生產代碼中)。
其他一些精度,如果有幫助的話:
我玩過的其他想法:
START TRANSACTION
語句,在測試之后發出ROLLBACK
:真的很臟,無法讓它工作個人觀點: @Transactional
+ @SpringBootTest
(在某種程度上)與spring.jpa.open-in-view
相同的反模式。 是的,一開始很容易讓事情正常工作,並且自動回滾很好,但它會失去很多靈活性和對事務的控制。 任何需要手動事務管理的東西都很難以這種方式進行測試。
我們最近有一個非常相似的案例,最后我們決定硬着頭皮改用@DirtiesContext
。 是的,測試需要多花 30 分鍾才能運行,但作為額外的好處,測試服務的行為方式與生產中的完全相同,並且測試更有可能發現任何事務問題。
但在我們進行切換之前,我們考慮使用以下解決方法:
interface TransactionService
{
void runWithoutTransaction(Runnable runnable);
}
@Service
public class RealTransactionService implements TransactionService
{
@Transactional(propagation = Propagation.NEVER)
public void runWithoutTransaction(Runnable runnable)
{
runnable.run();
}
}
#runWithoutTransaction
包裝外部 http 調用,例如:@Service
public class MyService
{
@Autowired
private TransactionService transactionService;
public void doStuff(UUID id)
{
transactionService.runWithoutTransaction(() -> {
// call an external service
})
}
}
這樣,您的生產代碼將執行Propagation.NEVER
檢查,並且對於測試,您可以將TransactionService
替換為沒有@Transactional
注釋的不同實現,例如:
@Service
@Primary
public class FakeTransactionService implements TransactionService
{
// No annotation here
public void runWithoutTransaction(Runnable runnable)
{
runnable.run();
}
}
這不僅限於Propagation.NEVER
。 其他傳播類型可以以相同的方式實現:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void runWithNewTransaction(Runnable runnable)
{
runnable.run();
}
最后 - 如果方法需要返回和/或接受一個值,則可以用Function
/ Consumer
/ Supplier
替換Runnable
參數。
這有點瘋狂,但是如果您使用的是 mysql 數據庫,那么也許切換到dolt進行測試?
Dolt 是一個 SQL 數據庫,您可以像 git 存儲庫一樣分叉、克隆、分支、合並、推送和拉取。
您可以將其包裝為testcontainers 容器,在啟動時加載必要的數據,然后在每次測試運行開始時加載 dolt reset 。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.