测试期间的Spring Boot JPA事务 - 不会在插入时抛出密钥违例异常

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了测试期间的Spring Boot JPA事务 - 不会在插入时抛出密钥违例异常相关的知识,希望对你有一定的参考价值。

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

如果我在第二次插入后执行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;
    

答案

当方法返回时,如何让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<>

@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;

@Entity之后,Hibernate必须为save()分配id。但它无法在不执行insert的情况下为实体获取id。

附:有一件事令我困惑。

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

以上是关于测试期间的Spring Boot JPA事务 - 不会在插入时抛出密钥违例异常的主要内容,如果未能解决你的问题,请参考以下文章

Spring boot + Hibernate + JPA 没有可用的事务性 EntityManager

Spring Boot - Mysql Driver - JPA - 在服务器运行发布请求很长时间后显示无法打开 JPA EntityManager 进行事务处理

Spring Boot 中的事务同步

Spring Boot-------JPA基础及查询规则

两小时上手Spring Boot (开发红包程序———通过JPA 连接Mysql ,事务等内容) 遇到的问题

Spring Boot 事务