弹簧测试对MariadB数据库进行的回滚更改无@Transactional
我有一个这样做的春季服务:
@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
}
}
传播。不表明我们在调用该方法时不得进行主动交易,因为我们不想在等待外部服务的答案时阻止与数据库的连接。
现在,我如何正确测试它,然后回滚数据库? @transactional在测试中无法使用,由于传播为止,会有一个例外。
@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?
我正在测试的数据库是Mariadb与TestContainers一起使用的,因此仅与Mariadb一起使用的解决方案/mysql对我来说就足够了。但是,更一般的事情会很棒!
(我希望在测试中不使用@transactional的另一个景象:有时我想测试交易边界正确放入代码中,并且在运行时没有达到一些懒惰的加载异常,因为我忘记了某个地方的@transactional在生产代码中)。
如果有帮助的话:
- 我将JPA与Hibernate一起使用,
- 时,数据库是用liquibase创建的
当应用程序上下文启动我播放的其他想法
- : @dirtiesContext:这要慢得多,创建一个新的上下文是更昂贵的不仅仅是在我的数据库中截断所有表中的所有表
- :死胡同,这只是一种返回交易中数据库状态的方法。 这将是理想的解决方案IMO,
- 如果我可以在全球范围内尝试使用连接, 请在测试前在数据源上发表启动交易语句,并且测试后
rollback
。肮脏,无法正常工作
I have a Spring service that does something like that :
@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
}
}
The Propagation.NEVER indicates we must not have an active transaction when the method is called because we don't want to block a connection to the database while waiting for an answer from the external service.
Now, how could I properly test this and then rollback the database ? @Transactional on the test won't work, there will be an exception because of 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();
}
}
I can remove the @Transactional but then my database is not in a consistent state for the next test.
For now my solution is to truncate all tables of my database after each test in a @AfterEach junit callback, but this is a bit clunky and gets quite slow when the database has more than a few tables.
Here comes my question : how could I rollback the changes done to my database without truncating/using @Transactional ?
The database I'm testing against is mariadb with testcontainers, so a solution that would work only with mariadb/mysql would be enough for me. But something more general would be great !
(another exemple where I would like to be able to not use @Transactional on the test : sometimes I want to test that transaction boundaries are correctly put in the code, and not hit some lazy loading exceptions at runtime because I forgot a @Transactional somewhere in the production code).
Some other precisions, if that helps :
- I use JPA with Hibernate
- The database is create with liquibase when the application context starts
Others ideas I've played with :
- @DirtiesContext : this is a lot slower, creating a new context is a lot more expensive than just truncating all tables in my database
- MariaDB SAVEPOINT : dead end, it's just a way to go back to a state of the database INSIDE a transaction. This would be the ideal solution IMO if i could work globally
- Trying to fiddle with connections, issuing
START TRANSACTION
statements natively on the datasource before the test andROLLBACK
after the tests : really dirty, could not make it work
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这是一个疯狂的想法,但是如果您使用的是mySQL数据库,则可以切换到 dolt 进行测试?
您可以将其包装为 testContainers容器,一开始就开始加载必要的数据每个测试运行 dolt reset 。
This is bit of wild idea, but if you are using mysql database, then maybe switch to dolt for tests?
You can wrap it as testcontainers container, load necessary data on start and then, on start of each test run dolt reset.
个人意见:
@transactional
+@springboottest
is(以某种方式)与spring.jpa.jpa.open-in-in-in-cive
。是的,首先让事情运行很容易,并且自动回滚很不错,但是它使您对交易的灵活性和控制权很失去。任何需要手动交易管理的东西都很难以这种方式进行测试。我们最近有一个非常相似的情况,最后我们决定咬住子弹并改用
@dirtiesContext
。是的,测试还需要30分钟的时间才能进行,但是作为额外的好处,测试服务的行为与生产中完全相同,并且测试更有可能捕获任何交易问题。但是在进行开关之前,我们考虑使用以下解决方法:
#runwithouttransaction
-method包装外部HTTP调用,例如:这样,您的生产代码将在
paspagation.never
检查中,并且对于测试,您可以使用没有其他没有@的实现来替换
注释,例如:transactionservice
交易这不仅限于
opagation.never
。可以以相同的方式实现其他传播类型:最后 - 可以用
函数/
消费者
/code>/替换参数
参数。供应商如果该方法需要返回和/或接受值。Personal opinion:
@Transactional
+@SpringBootTest
is (in a way) the same anti-pattern asspring.jpa.open-in-view
. Yes, it's easy to get things working at first and having the automatic rollback is nice, but it loses you a lot of flexibility and control over your transactions. Anything that requires manual transaction management becomes very hard to test that way.We recently had a very similar case and in the end we decided to bite the bullet and use
@DirtiesContext
instead. Yeah, tests take 30 more minutes to run, but as an added benefit the tested services behave the exact same way as in production and the tests are more likely to catch any transaction issues.But before we did the switch, we considered using the following workaround:
#runWithoutTransaction
-Method, e.g.:That way your production code will peform the
Propagation.NEVER
check, and for the tests you can replace theTransactionService
with a different implemention that doesn't have the@Transactional
annotations, e.g.:This is not limited to
Propagation.NEVER
. Other propagation types can be implemented in the same way:And finally - the
Runnable
parameter can be replaced with aFunction
/Consumer
/Supplier
if the method needs to return and/or accept a value.