如何通过JPA注解在含额外列的多对多关系中自动删除孤立Tag实体
多对多关联(含中间表额外字段)的级联删除问题
问题背景
我们有Post和Tag两个实体,通过中间实体PostTag映射多对多关联(用于给关联表添加额外字段),数据库表结构如下:
Post表
| id | name |
|---|---|
| 1 | Post 1 |
| 2 | Post 2 |
Tag表
| id | name |
|---|---|
| 1 | Tag 1 |
| 2 | Tag 2 |
Post_Tag关联表
| end_date | post_id | tag_id |
|---|---|---|
| 2020-10-27 13:44:17.045000 | 1 | 1 |
| 2020-10-27 13:44:17.045000 | 2 | 1 |
| 2020-10-27 13:44:17.057000 | 2 | 2 |
待解决场景
- 删除Post表中id=2的Post时,能否自动删除Tag表中id=2的Tag?且不能删除Tag表中id=1的Tag。
- 删除Tag(id=2)与Post(id=2)之间的关联时,能否自动删除Tag表中id=2的Tag?
当前实体代码实现
Post实体
@Entity() @Table(name = "post") public class Post { @Id @Column(name = "id") @Type(type = "uuid-char") @GeneratedValue(generator = "uuid") @GenericGenerator(name = "uuid", strategy = "uuid2") private UUID id; private String title; @OneToMany( mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true ) private Set<PostTag> tags = new HashSet<>(); public Post() { } public Post(String title) { this.title = title; } //Getters and setters omitted for brevity public void addTag(Tag tag, ZonedDateTime zonedDateTime) { PostTag postTag = new PostTag(this, tag); postTag.setEndDate(zonedDateTime); tags.add(postTag); } public void removeTag(Tag tag) { for (Iterator<PostTag> iterator = tags.iterator(); iterator.hasNext(); ) { PostTag postTag = iterator.next(); if (postTag.getPost().equals(this) && postTag.getTag().equals(tag)) { iterator.remove(); postTag.setPost(null); postTag.setTag(null); } } } }
Tag实体
@Entity() @Table(name = "tag") public class Tag { @Id @Column(name = "id") @Type(type = "uuid-char") private UUID id; private String name; public Tag() { } public Tag(UUID id, String name) { this.id = id; this.name = name; } //Getters and setters omitted for brevity }
PostTagId(复合主键)
@Embeddable public class PostTagId implements Serializable { @Column(name = "post_id") @Type(type = "uuid-char") private UUID postId; @Column(name = "tag_id") @Type(type = "uuid-char") private UUID tagId; PostTagId() {} public PostTagId(UUID postId, UUID tagId) { this.postId = postId; this.tagId = tagId; } //Getters and setters omitted for brevity }
PostTag中间实体
@Entity() @Table(name = "post_tag") public class PostTag { @EmbeddedId private PostTagId id; @ManyToOne(fetch = FetchType.LAZY) @MapsId("postId") private Post post; @ManyToOne(fetch = FetchType.LAZY) @MapsId("tagId") private Tag tag; // extra column @Column(name = "end_date") private ZonedDateTime endDate; PostTag() {} public PostTag(Post post, Tag tag) { this.post = post; this.tag = tag; this.id = new PostTagId(post.getId(), tag.getId()); } public PostTagId getId() { return id; } //Getters and setters omitted for brevity }
当前业务方法实现
场景1:删除Post
public void deletePost(Post post) { postRepository.delete(post); }
场景2:移除Post与Tag的关联
public void removeTag(Post post, Tag tag) { post.removeTag(tag); postRepository.save(post); }
问题解答
场景1:删除Post(id=2)时自动删除Tag(id=2)
当前配置无法直接实现,现有Post到PostTag的级联规则仅会删除关联的PostTag记录,不会触发Tag实体的删除。要实现需求,需手动判断Tag是否还有其他关联:
- 遍历待删除Post关联的所有
PostTag记录; - 对每个关联的Tag,统计其在
Post_Tag表中的关联数量; - 若关联数量仅剩当前Post的这一条,则删除该Tag;否则仅删除
PostTag记录。
修改后的deletePost方法示例:
public void deletePost(Post post) { // 用HashSet避免遍历过程中集合结构变更报错 for (PostTag postTag : new HashSet<>(post.getTags())) { Tag tag = postTag.getTag(); // 统计该Tag的关联数量 long关联数 = postTagRepository.countByTagId(tag.getId()); if (关联数 <= 1) { tagRepository.delete(tag); } } postRepository.delete(post); }
场景2:移除Post(id=2)与Tag(id=2)的关联时自动删除Tag(id=2)
当前配置同样无法直接实现,移除关联仅会删除PostTag记录,不会触发Tag删除。实现需求需在移除关联后检查Tag的剩余关联数:
- 执行移除关联的操作;
- 统计该Tag在
Post_Tag表中的剩余关联数量; - 若剩余数量为0,则删除该Tag。
修改后的removeTag方法示例:
public void removeTag(Post post, Tag tag) { post.removeTag(tag); postRepository.save(post); // 统计Tag剩余关联数 long剩余关联数 = postTagRepository.countByTagId(tag.getId()); if (剩余关联数 == 0) { tagRepository.delete(tag); } }
注:需确保postTagRepository提供countByTagId(UUID tagId)查询方法,用于统计Tag的关联数量。
内容的提问来源于stack exchange,提问作者Bogdan Constantin Popovici
相关产品推荐
相关产品推荐

