【问题标题】:Spring Boot JPA Transaction during Test - not throwing key violation exception on insert测试期间的 Spring Boot JPA 事务 - 插入时不抛出键冲突异常
【发布时间】:2023-03-20 17:20:02
【问题描述】:

我有以下测试,它会生成带有公司 ID 和电子邮件的邀请。然后,我们使用相同的公司 ID 和电子邮件生成第二个。这应该违反数据库的唯一约束(参见邀请实体)。事情是,测试通过了,如下所示(我猜第二次插入的事务永远不会提交)

如果我在第二次插入后执行findAll()(请参阅下面的评论),我会在findAll() 发生索引违规的那一行得到一个异常。

如何使UserService 中的方法在方法返回时提交其事务,以便测试代码按原样抛出异常?

@RunWith(SpringRunner.class)
@DataJpaTest
public class UserServiceTest {
    ... Everything is autowired
    @Test
    public void testCreateInvitation() {
        String email = "test2@example.com";
        Company company = userService.createCompany("ACMETEST", tu);

        assertTrue(invitationRepository.count() == 0l);
        assertNotNull(userService.sendInvitation(company, email));
        assertTrue(invitationRepository.count() == 1l);

        assertTrue(invitationRepository.findAllByEmail(email).size() == 1);
        // Second should throw exception
        userService.sendInvitation(company, email);

        // If this line is uncommented, I get key violation exception, otherwise, test passes!!!!????
        //Iterable<Invitation> all = invitationRepository.findAll();
    }

邀请类和约束:

@Entity
@Table(uniqueConstraints=@UniqueConstraint(columnNames={"email", "company_id"}))
@JsonIdentityInfo(generator= ObjectIdGenerators.PropertyGenerator.class, property="id")
@Data
public class Invitation {

    @Id
    @GeneratedValue(generator="system-uuid")
    @GenericGenerator(name="system-uuid", strategy = "uuid")
    String id;

    @ManyToOne
    @JoinColumn(name="company_id")
    private Company company;

    String email;

    Date inviteDate;

    Date registerDate;
}

用户服务:

@Service
@Transactional(propagation = Propagation.REQUIRES_NEW)
public class UserService {
    public Invitation sendInvitation(Company company, String toEmail) {
        Invitation invitation = new Invitation();
        invitation.setCompany(company);
        invitation.setInviteDate(new Date());
        invitation.setEmail(toEmail);
        try {
            // TODO send email
            return invitationRepository.save(invitation);
        } catch (Exception e) {
            LOGGER.error("Cannot send invitation: {}", e.getMessage());
        }
        return null;
    }
}

【问题讨论】:

  • 您在测试中使用的是哪个数据库?
  • h2,spring boot 默认
  • 再次检查是否确实对数据库进行了约束。
  • 确实如此,如果我取消注释下面的查询,请参阅有关抛出唯一约束违规异常的部分...
  • 遇到同样的问题 -> stackoverflow.com/questions/52697057/…

标签: java spring spring-boot spring-data-jpa


【解决方案1】:

如何让 UserService 中的方法在方法返回时提交其事务,以便测试代码按原样抛出异常?

因为@DataJpaTest 本身就是@Transactional。用@Test 方法编写的代码在事务中执行。您可以使用Propogation.NOT_SUPPORTED 更改它

非事务性执行,如果存在则暂停当前事务

@Test
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void testCreateInvitation() {
    // your test method body unchanged
}

使用这个你应该在第二个userService.sendInvitation(company, email); 调用中得到一个异常。

还有一些其他的选择。

选项 1:

@Commit 添加到您的测试方法中。

所有标有@Transactional 的测试在执行后都是rolled back。但是 Hibernate 有一个微妙的事情:它可能会延迟执行 sql,直到您提交事务(或显式调用 flush(选项 2))。

由于事务回滚,因此没有执行任何插入操作,因此没有违反约束。

请注意,使用 @Commit 您会在测试方法外部抛出异常。

选项 2:

使用手册flush():

如果你的仓库extends JpaRepository&lt;&gt;:

@Test
public void testCreateInvitation() {
    // create first invitation,
    // create second invitation
    invitationRepository.flush();
}

否则注入 EntityManager 并在其上调用 flush()

@PersistenceContext
private EntityManager em;

@Test
public void testCreateInvitation() {
    // create first invitation,
    // create second invitation
    em.flush();
}

invitationRepository.flush()em.flush() 被调用时,你会得到一个异常。

选项 3: 使用 GenerationType.IDENTITY:

@Id
@GeneratedValue(strategy = IDENTITY)
private Long id;

Hibernate 必须在 save() 之后将 id 分配给 @Entity。但是如果不执行insert,它就无法获取实体的ID。

附言 有一件事让我很困惑。

您使用@Transactional(propagation = Propagation.REQUIRES_NEW),它应该在方法执行后强制提交事务。请检查您的日志以查看交易是否真正有效。

【讨论】:

  • 这是正确的,特别是Propagation.NOT_SUPPORTED 谢谢。几个其他 cmets:1 - @Transactional(propagation = Propagation.REQUIRES_NEW) 不起作用,可能是因为它是 only 事务? 2 - 我在存储库中没有 flush() 方法。 3 - 我没有尝试 IDENTITY 更改,因为它正在工作。
  • @mikeb 1 - 不。任何对服务方法的调用都应在新事务中执行,而不管在外部启动的任何事务。我建议您分享您的项目的最小版本,其中问题是可以观察到的,这样我们就可以弄清楚为什么会发生这种情况。 2 - 我编辑了我的答案,见选项 2
  • @mikeb 或者...你自己实例化 UserService 吗?
猜你喜欢
  • 1970-01-01
  • 2021-10-03
  • 1970-01-01
  • 1970-01-01
  • 2016-09-13
  • 2018-12-06
  • 1970-01-01
  • 2020-06-02
  • 2018-02-04
相关资源
最近更新 更多