如何在活动的Spring事务中将数据刷新到数据库?

21

我希望使用Spring测试框架来测试Hibernate Session的save()方法。 @Test方法如下:

@Test
@Transactional
public void testSave() {
    User expected = createUser();
    getGenericDao().currentSession().save(expected);
    User actual = getUser(generatedId);
    assertUsersEqual(expected,actual);
}

我想将用户刷新到数据库中。在这个方法之后,我希望我的用户出现在数据库中。

getGenericDao().currentSession().save(expected);

然后我想使用Spring Data框架访问数据库,并通过下一行获取保存的用户:

然后我想使用 Spring Data 框架访问数据库,并通过下面的代码获取已保存的用户:

User actual = getUser(generatedId);

我尝试使用Hibernate的flush方法,例如:

currentSession().setFlushMode(MANUAL);
//do saving here
currentSession().flush();

它无法将我的用户刷新到数据库中!但是如果我不使用@Transactional Spring注解并在编程式Spring事务中保存我的用户,则可以实现我想要的效果。但是,由于没有Spring @Transactional,因此保存到数据库中的用户不会回滚。因此,我的测试方法会更改数据库和随后的测试方法的行为。

因此,我需要在测试方法内部刷新我的用户到数据库中(而不是在末尾),并在测试方法结束时将所有更改回滚到数据库。

更新建议准备方法如下:

@Transactional
public void doSave(User user){
    getGenericDao().currentSession().save(user);
}

在testSave函数中调用doSave函数没有任何作用。执行此方法后,我在数据库中仍然找不到用户。我设置了断点并从命令行检查了我的数据库。

更新非常感谢您的回复。问题是flush()方法没有将我的用户放入数据库中。我尝试了Isolation.READ_UNCOMMITTED,但它没有将我的用户放入数据库中。我只有在关闭@Test方法上的Spring事务并以编程方式进行保存时才能实现想要的效果。但是,@Test方法将不会回滚,为随后的@Test方法保留已保存的用户。在这里,保存用户的@Test方法并不像删除用户的@Test方法那样危险,因为它不会回滚。因此,必须对带有@SpringTransactional支持的@Test方法进行Spring事务处理,但我无论如何都不能将我的用户(或删除)放入数据库中。实际上,只有在@Test方法结束并且@Test方法的事务被提交之后,用户才会被(删除)放入数据库中。因此,我希望在@Test方法中间将我的用户保存到数据库中,并在@Test方法结束时将其回滚

谢谢!


你能给我们提供你的GenericDao类吗? 编辑:实际上,我想我看到了问题所在。当前事务需要在数据实际保存到数据库之前提交。因此,你需要两个@Transactional方法:一个用于保存,另一个用于加载。 - Martin Wickham
请查看我是否正确地完成了。 - Volodymyr Levytskyi
关闭。我的意思是有两个事务函数被一个非事务函数调用。那应该可以工作... - Martin Wickham
如果我将我的testSave方法设置为非事务性,那么在testSave方法结束后就不会有回滚。因此保存的用户将保留。 - Volodymyr Levytskyi
当前线程未找到任何会话。当调用GenericDao的save方法时,这是Hibernate异常。在doSave上使用@Transactional不起作用! - Volodymyr Levytskyi
4个回答

15

最终,我坚持以下解决方案:

首先,我的@Test方法不在spring的@Transactional支持下运行。请参阅此文章以了解其可能有多危险。接下来,我在@Test方法中不使用@Repository bean,而是自动装配使用@Transactional注解的@Service bean。

奇迹就在于像这样的@Test方法
@Test
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void testSave() {
    Answer created = createAnswer();
    Long generatedId = answerService.save(created);
//at this moment answer is already in db
    Answer actual=getAnswerById(generatedId);
... }

将我的Answer对象放入数据库(在answerService.save(created);之后),并且方法getAnswerById访问数据库以提取并检查保存是否正确。
为了消除在@Test方法中对数据库所做的更改,我通过JdbcTestUtils.executeSqlScript重新创建数据库。


3
可以使用 @DirtiesContext 注解来代替使用 JdbcTestUtils。 - Rosty Kerei

7
  1. 请查看此处,其中有关于@Transactional测试的警告(Spring陷阱:事务测试被认为是有害的)。我在我的服务测试中使用了@org.springframework.test.context.jdbc.Sql来重新填充数据库,并在控制器中使用了@Transactional
  2. 当提交事务时,仅会在控制器更新测试中使用无效数据时抛出ConstraintViolationException。因此,我找到了3个选项:
    • 2.1 使用@Commit@Transactional(propagation = Propagation.NEVER)注释测试。注意数据库更改。
    • 2.2 使用TestTransaction

代码:

     TestTransaction.flagForCommit();
     TestTransaction.end();
  • 2.3 使用 TransactionTemplate

代码:

    @Autowired
    private PlatformTransactionManager platformTransactionManager;

    @Test(expected = Exception.class)
    public void testUpdate() throws Exception {
        TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager);
        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        String json = ...
        transactionTemplate.execute(ts -> {
            try {
                mockMvc.perform(put(REST_URL + USER_ID)
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(json))
                    .andExpect(status().isOk());
                ...
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        });

4
你还应该注意导入的包: 在我的情况下,我导入了

import javax.transaction.Transactional;

而不是

import org.springframework.transaction.annotation.Transactional;

请注意区分。

1
如果flush不起作用,则非常依赖于您的数据库隔离级别。 隔离是数据库的ACID属性之一,它定义了一个操作所做的更改何时/如何对其他并发操作可见。
我相信您的隔离级别设置为Read CommittedRepeatable Read

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接