当前位置: 首页 > 知识库问答 >
问题:

Spring boot-多对多关联未删除联接表数据

凤昊东
2023-03-14

我在Spring Boot中遇到了一个多对多关系问题。代码如下:


public class Task {

  @Id
  @GeneratedValue
  private Long id;

  @ManyToMany(cascade = {PERSIST, MERGE}, fetch = EAGER)
  @JoinTable(
      name = "task_tag",
      joinColumns = {@JoinColumn(name = "task_id", referencedColumnName = "id")},
      inverseJoinColumns = {@JoinColumn(name = "tag_id", referencedColumnName = "id")}
  )
  @Builder.Default
  private Set<Tag> tags = new HashSet<>();

  public void addTags(Collection<Tag> tags) {
    tags.forEach(this::addTag);
  }

  public void addTag(Tag tag) {
    this.tags.add(tag);
    tag.getTasks().add(this);
  }

  public void removeTag(Tag tag) {
    tags.remove(tag);
    tag.getTasks().remove(this);
  }

  public void removeTags() {
    for (Iterator<Tag> iterator = this.tags.iterator(); iterator.hasNext(); ) {
      Tag tag = iterator.next();
      tag.getTasks().remove(this);
      iterator.remove();
    }
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Task)) return false;
    return id != null && id.equals(((Task) o).getId());
  }

  @Override
  public int hashCode() {
    return id.intVal();
  }
}

public class Tag {

  @Id
  @GeneratedValue
  private Long id;

  @NotNull
  @Column(unique = true)
  private String name;

  @ManyToMany(cascade = {PERSIST, MERGE}, mappedBy = "tags", fetch = EAGER)
  @Builder.Default
  private final Set<Task> tasks = new HashSet<>();

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Tag tag = (Tag) o;
    return Objects.equals(name, tag.name);
  }

  @Override
  public int hashCode() {
    return id.intVal();
  }

}

当然,我有task_tag表,在其中,在任务中插入标签并保存该任务后,会出现一个条目。但是,当我删除标签(或清除它们)时,条目不会从连接表中删除。这是测试:

@Test
  void entityIntegration() {
    Task task = taskRepo.save(...);

    Tag tag1 = Tag.builder().name(randomString()).build();
    Tag tag2 = Tag.builder().name(randomString()).build();
    Tag tag3 = Tag.builder().name(randomString()).build();
    Tag tag4 = Tag.builder().name(randomString()).build();
    final List<Tag> allTags = Arrays.asList(tag1, tag2, tag3, tag4);
    tagRepo.saveAll(allTags);

    task.addTag(tag1);
    taskRepo.save(task);
    final Long task1Id = task.getId();
    assertTrue(tag1.getTasks().stream().map(Task::getId).collect(Collectors.toList()).contains(task1Id));

    task.clearTags();
    task = taskRepo.save(task);
    tag1 = tagRepo.save(tag1);
    assertTrue(task.getTags().isEmpty());
    assertTrue(tag1.getTasks().isEmpty());

    task.addTags(allTags);
    task = taskRepo.save(task); // FAILS, duplicate key ...

  }

task\u tag表确实在这两列(并且仅在这两列)上形成了一个复合索引。

我做错了什么?我遵循了每一条建议和建议——使用集合代替列表,使用助手方法,清理等等。。。我找不到窃听器。

非常感谢。

共有2个答案

苗征
2023-03-14

请将DELETE关键字添加到多对多注释的cascade属性中。我认为Tag类的task属性的注释应该更改如下。

你可以试试下面的映射


public class Task {

  @Id
  @GeneratedValue
  private Long id;

  @ManyToMany(cascade = {PERSIST, MERGE,DELETE}, fetch = EAGER)
  @JoinTable(
      name = "task_tag",
      joinColumns = {@JoinColumn(name = "task_id", referencedColumnName = "id")},
      inverseJoinColumns = {@JoinColumn(name = "tag_id", referencedColumnName = "id")}
  )
  @Builder.Default
  private Set<Tag> tags = new HashSet<>();

  public void addTags(Collection<Tag> tags) {
    tags.forEach(this::addTag);
  }

  public void addTag(Tag tag) {
    this.tags.add(tag);
    tag.getTasks().add(this);
  }

  public void removeTag(Tag tag) {
    tags.remove(tag);
    tag.getTasks().remove(this);
  }

  public void removeTags() {
    for (Iterator<Tag> iterator = this.tags.iterator(); iterator.hasNext(); ) {
      Tag tag = iterator.next();
      tag.getTasks().remove(this);
      iterator.remove();
    }
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Task)) return false;
    return id != null && id.equals(((Task) o).getId());
  }

  @Override
  public int hashCode() {
    return id.intVal();
  }
}
public class Tag {

  @Id
  @GeneratedValue
  private Long id;

  @NotNull
  @Column(unique = true)
  private String name;

  @ManyToMany(cascade = {PERSIST, MERGE,DELETE}, mappedBy = "tags", fetch = EAGER)
@JoinTable(
      name = "task_tag",
      joinColumns = {@JoinColumn(name = "tag_id", referencedColumnName = "id")},
      inverseJoinColumns = {@JoinColumn(name = "task_id", referencedColumnName = "id")}
  )
  @Builder.Default
  private final Set<Task> tasks = new HashSet<>();

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Tag tag = (Tag) o;
    return Objects.equals(name, tag.name);
  }

  @Override
  public int hashCode() {
    return id.intVal();
  }

}

公孙棋
2023-03-14

让我印象最深刻的是,你的标签是相等的,哈希码是不匹配的。

您的“equals”基于对象的名称相同来驱动相等,这对“标签就是名称”的咒语有逻辑意义。但是基于“id”的哈希码驱动是等价的,根本不使用名称。

暂时忘记JPA/Hibernate,只是当这两个不同步时,普通的旧集合本身变得非常不可预测。

您可以在这里阅读更多关于这一点的信息,特别是为什么不匹配相等的哈希代码最终会哈希到错误的bucket,并导致在hashset中保持其完整性的混乱:为什么我需要重写Java中的equals和hashCode方法?

有很多方法可以使它们重新同步(使用像Lombok这样的库和IDE中的代码生成工具),但我不想指定一种,而是简单地指向这个web资源,它方便地为他的示例创建了一个具有完全相同概念的标记,所以我怀疑您可以自己使用完全相同的模式。

https://vladmihalcea.com/the-best-way-to-use-the-manytomany-annotation-with-jpa-and-hibernate/

这里有另一个有用的SO线程,我发现它讨论了关系和身份/相等/哈希代码,因为它影响JPA:JPA哈希代码()/相等()困境

 类似资料:
  • 我有两个多对多关联的表。 DB详细信息:用户-->列[Id,name]

  • 问题内容: 我有两个与多对多关联的表。 —数据库片段: 加载 ID 名称 会话 ID 日期 sessionsloads LoadId 的SessionID —hibernate映射片段: 为了从关联表 sessionloads中 删除一个条目,我执行以下代码: 但是,启动后,此代码将保持不变。 删除关联的正确方法是什么? 问题答案: 您需要更新和之间的链接的两端: 实际上,许多开发人员使用防御性方

  • 问题内容: 问题: 我 在* 两个实体 A和B 之间 有 多对多关联 。我将 A实体 设置为其 关系 的 所有者 (inverse = true在b.hbm.xml中A的集合上)。 * 当我 删除一个A实体时 , 联接表中的 相应 记录也会被删除 。 当我 删除一个B实体时 , 联接表中的 相应 记录不会被删除 (完整性违反异常)。 - 让我们考虑一些非常简单的 示例 : 文件 a.hbm.xml

  • 本文向大家介绍postgresql 实现多表关联删除,包括了postgresql 实现多表关联删除的使用技巧和注意事项,需要的朋友参考一下 t_aj_ajjbxx t_aj_ajfbxx t_xt_dwxx 两表关联,删除一张表中数据 只有t_aj_ajfbxx 表中数据被删除 三表关联,删除一张表中数据 只有t_aj_ajjbxx 表中数据被删除 补充:PostgreSQL的级联删除(主键删除则

  • 问题内容: 我在我的数据库3个表:,和 学生可以有多个课程,课程可以有多个学生。和之间存在多对多关系。 我为我的项目和课程添加了3个案例。 (a)当我添加用户时,它会保存得很好, (b)当我为学生添加课程时,它会在-预期行为中创建新的行。 (三)当我试图删除学生,则在删除适当的记录和,但它也删除其中不需要的记录。即使课程中没有任何用户,我也希望课程在那里。 下面是我的表和注释类的代码。 这是Hib

  • 问题内容: 我在我的数据库3个表:,和 学生可以有多个课程,课程可以有多个学生。和之间存在多对多关系。 我为我的项目和课程添加了3个案例。 (a)当我添加用户时,它会保存得很好, (b)当我为学生添加课程时,它将在-预期行为中再次创建新行。 (三)当我试图删除学生,则在删除适当的记录和,但它也删除其中不需要的记录。即使课程中没有任何用户,我也希望课程在那里。 下面是我的表和注释类的代码。 这是Hi