【问题标题】:Spring Boot test @After not deleting rows before next testSpring Boot 测试 @After 在下一次测试之前不删除行
【发布时间】:2019-12-31 13:59:17
【问题描述】:

我的 Spring Boot 集成测试中有以下 @Before 和 @After:

@Before
public void setup() {
    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
        .addFilter(springSecurityFilterChain).build();

    user = userRepository.save(
        new User("Joe", "Bloggs", "joe@example.com", "joe", passwordEncoder.encode("secret")));

    currency = currencyRepository.save(
        new Currency("GBP", "£%01.2f"));

    fund = fundRepository.save(
        new Fund("Nationwide", (double) 100, currency));

}

@After
public void teardown() {
    userRepository.delete(user);
    currencyRepository.delete(currency);
    fundRepository.delete(fund);
}

但是,每次测试后似乎都没有删除货币,而且我的测试错误地失败了:

...
[ERROR] testGetFunds_whenNoToken_thenUnauthorized(biz.martyn.budget.FundsControllerTest)  Time elapsed: 3.268 s  <<< ERROR!
org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find biz.martyn.budget.currency.Currency with id 437; nested exception is javax.persistence.EntityNotFoundException: Unable to find biz.martyn.budget.currency.Currency with id 437
Caused by: javax.persistence.EntityNotFoundException: Unable to find biz.martyn.budget.currency.Currency with id 437
...

之后,如果我查询测试数据库,我看到行没有被删除:

mysql> select * from currencies;
+----+---------------------+---------------------+---------------+------+---------------------+
| id | created_at          | deleted_at          | format        | name | updated_at          |
+----+---------------------+---------------------+---------------+------+---------------------+
...
| 437 | 2020-01-02 13:51:24 | 2020-01-02 13:51:23 | &pound;%01.2f | GBP  | 2020-01-02 13:51:24 |
...
+----+---------------------+---------------------+---------------+------+---------------------+
5 rows in set (0.00 sec)

名称应该只有一个唯一条目,但我猜由于删除没有发生,它正在为“GBP”提取重复项。我的货币存储库:

Currency.java

@Entity(name = "currencies")
@SQLDelete(sql = "UPDATE currencies SET deleted_at = now() WHERE id = ?")
@Where(clause = "deleted_at is null")
public class Currency {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  protected Integer id;

  @Column(unique = true, nullable = false)
  private String name;

  @Column(nullable = false)
  private String format;

  @Column(name = "created_at", updatable = false)
  @CreationTimestamp
  protected LocalDateTime createdAt;

  @Column(name = "updated_at")
  @UpdateTimestamp
  protected LocalDateTime updatedAt;

  @Column(name = "deleted_at")
  protected LocalDateTime deletedAt;

  protected Currency() {}

  public Currency(String name, String format) {
    this.name = name;
    this.format = format;
  }

  public Integer getId() {
    return id;
  }

  public String getName() {
    return name;
  }

  public void setName(final String name) {
    this.name = name;
  }

  public String getFormat() {
    return format;
  }

  public void setFormat(final String format) {
    this.format = format;
  }
}

用户.java

@Entity(name = "users")
public class User implements Serializable {

    /**
     *
     */
    private static final long serialVersionUID = -8507204786382662588L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false)
    private String firstName;

    @Column(nullable = false)
    private String surname;

    @Column(nullable = false, unique = true)
    private String email;

    @Column(nullable = false, unique = true)
    private String username;

    @Column(nullable = false)
    @JsonIgnore
    private String password;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name="user_id")
    @JsonIgnore
    private List<Fund> funds;

    protected User() {}

    public User(String firstName, String surname, String email, String username, String password) {
        this.firstName = firstName;
        this.surname = surname;
        this.email = email;
        this.username = username;
        this.password = password;
    }

    public Long getId() {
        return id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    // standard getters and setters

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public List<Fund> getFunds() {
        return funds;
    }

    public void addFund(Fund fund) {
        funds.add(fund);
        fund.setUser(this);
    }

    public void removeFund(Fund fund) {
        funds.remove(fund);
        fund.setUser(null);
    }

    // public Fund getFund(int id) {
    //  fundRepository.findByIdAndUserId(id)
    //         .orElseThrow(() -> new EntityNotFoundException("Fund ID not found: "+id));
    // }
}

【问题讨论】:

  • 是 SpringBootTest 还是 DataJpaTest?
  • 索引 UK_abaar0d2ymp2spkrkjnekr1h8 是如何定义的?
  • 这是 SpringBootTest。我不知道 UK_... 字符串是在哪里定义的,我的列或属性都没有这样命名。
  • @Transactional注释你处理数据库操作的测试类,你应该准备好了;否则尝试使用@DataJpaTest
  • 货币实体或任何其他实体属性中是否有任何属性声明为唯一?

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


【解决方案1】:

delete 方法接受Currency 的对象。您的 Currency 对象有一个自动生成的 ID。

当您将传递给 save 的同一对象传递给 delete 时,您没有设置 ID,这就是删除操作从未真正删除您要删除的数据的原因。

您可以使用从 save 方法返回的对象,也可以获取生成的 ID 并使用 deleteById 方法。

这里是一个使用对象删除的例子。

@Before
public void setup() {
    user = repository.save(user);
}
@After
public void tearDown() {
    repository.delete(user);
}

或者,您可以使用相同的对象来获取生成的 ID 并使用 deleteById 方法。

如果您查看为 JPA 提供实现的 SimpleJpaRepository,您会发现如果您的 ID 为 null,那么它们将被视为新实体并且永远不会被删除。

    /*
     * (non-Javadoc)
     * @see org.springframework.data.repository.CrudRepository#delete(java.lang.Object)
     */
    @Override
    @Transactional
    @SuppressWarnings("unchecked")
    public void delete(T entity) {

        Assert.notNull(entity, "Entity must not be null!");

        if (entityInformation.isNew(entity)) {
            return;
        }

        // Other stuff
    }

【讨论】:

  • 嗨。谢谢回复。我假设它只会在 save() 中新启动的实例中设置 ID,更新我的代码并立即发布。无论如何,仍然看到错误但与以前不同,所以我也更新了。它抱怨货币 ID 不存在,但在测试运行后我可以在 MySQL 控制台中看到它。另外,奇怪的是用户被删除了,最后我有一个空表。 User 和 Currency/Fund 之间的唯一区别是 User 实现了序列化。试过了,但还是一样。
  • @Martyn - 如果您也可以发布User 实体会更好。你有SQLDelete 用户实体吗?如果您有类似的更新查询,则该表不应从表中删除行。
  • 嗨。我现在添加了用户。没错,它是唯一没有 SQLDelete 的。它应该有,但我一定忘了添加它。但是,我认为 delete() 会处理它,因为它也是 Hibernate 的注释。在实现软删除时我应该使用另一种“删除”方法吗?
  • @Martyn,现在您的问题与您最初提出的问题有很大不同。在最初的问题中,您遇到了未删除记录的问题。现在的要求非常不同。我建议创建一个新问题,以便如果有人遇到与您相同的问题,可以参考此问题寻求帮助。如果这里的答案解决了您最初提到的问题,请记得点赞或接受答案。
  • 当然。我仍然遇到问题,但它可能与 SQLDelete 附加到某些实体而不是其他实体有关。我可能需要在该领域多花一点时间,并在需要时报告。我要删除这个帖子吗?它并没有真正解决问题,所以可能对任何人都没有用。
猜你喜欢
  • 2019-07-19
  • 1970-01-01
  • 2017-08-28
  • 2021-12-13
  • 2017-01-17
  • 2023-01-22
  • 2016-01-01
  • 2019-07-22
  • 2021-12-21
相关资源
最近更新 更多