【发布时间】:2021-07-07 17:41:15
【问题描述】:
我在 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_tag 表确实在这两个(也是唯一的)列上形成了一个复合索引。
我做错了什么?我遵循了每一个建议和建议 - 使用集合而不是列表,使用辅助方法,清理等...... 我找不到错误。
谢谢!
【问题讨论】:
-
getClass().hashCode()(即一个常量)是您可能从对象的hashCode实现中返回的最差值 -
无关,但我改了。
-
task.addTags(allTags)行之后,如果在 foreach 循环中打印task.getTags(),有多少个?你在使用Lists 时遇到过同样的问题吗?另外,当您从Tag.tasks映射中删除cascade = {PERSIST, MERGE}时,我会检查它是否有效? -
为什么需要双向映射?我建议标签不应该知道这些任务。无论如何,您确定您的
clearTags或removeTags实现工作正常,即它从任务集中正确删除了任务?如果将对象添加到基于哈希的集合中,例如您的情况,则在 equals/hashCode 中使用 id 可能会出现问题。尝试在持久化后完全重新加载数据(即在生成和设置 id 之后) -
是的,那些辅助方法确实可以正常工作
标签: spring spring-boot hibernate jpa many-to-many