Tagbangers Blog

自己参照で双方向関連を持つデータを削除する(delete bidirectional self reference data)

双方向の関連をもつ自己参照テーブルのデータを消したいときに、以下のEntityの書き方では外部キー制約のためエラーとなってしまいました。

@Entity
@Table(name="article")
@PrimaryKeyJoinColumn
@DynamicInsert
@DynamicUpdate
public class Article {
    @ManyToMany
    @JoinTable(
            name="article_related_article",
            joinColumns = { @JoinColumn(name="article_id")},
            inverseJoinColumns = { @JoinColumn(name="related_id") })
    private Set<Article> relatedToArticles = new HashSet<>();
    @ManyToMany(mappedBy="relatedToArticles")
    private Set<Article> relatedByArticles = new HashSet<Article>();
}
@Service
@Transactional(rollbackFor=Exception.class)
public class ArticleService {
    @CacheEvict(value="articles", allEntries=true)
    public Article deleteArticle(ArticleDeleteRequest request, BindingResult result) throws BindException {
        Article article = articleRepository.findByIdForUpdate(request.getId(), request.getLanguage());
        articleRepository.delete(article);
        return article;
    }
}

関連するArticleをもつ、またはそのArticleが別のArticleから関連を持たれている状態でdeleteArticleを実行すると

ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Cannot delete or update a parent row: a foreign key constraint fails (`database`.`article_related_article`, CONSTRAINT `FK_g*****` FOREIGN KEY (`related_id`) REFERENCES `article` (`id`))

というエラーがでてしまいます。

mappedByをつけた@ManyToManyはこれをつけた方がもう一方の親という関係性を示唆するようですが
今回は関連を持つ側・持たれている側のどちらでも処理を行えるようにしたいので
以下のようにEntityを変更

public class Article {
    @ManyToMany
    @JoinTable(
            name="article_related_article",
            joinColumns = { @JoinColumn(name="article_id")},
            inverseJoinColumns = { @JoinColumn(name="related_id") })
    private Set<Article> relatedToArticles = new HashSet<>();
    @ManyToMany
    @JoinTable(
            name="article_related_article",
            joinColumns = { @JoinColumn(name="related_id")},
            inverseJoinColumns = { @JoinColumn(name="article_id") })
    private Set<Article> relatedByArticles = new HashSet<>();
}

一方向関連の書き方をどちらのメンバにも付与した形?です。
Hbm2ddlでsqlが自動的に生成されるか確認したところ問題なく目的のcreate文が作成できたので、この形でよさそうです。
これでdeleteArticleを実行すると、関連を削除しつつ、記事の削除ができるようになりました。