Hibernate入门之many to many关系映射详解

前言

上一节我们讲解完了一对多映射,本节我们进入到关系映射最后一节即多对多关系映射,文中若有错误之处,还望指正。

many to many关系映射 

本节我们所给出的实体是post和tag,发表一篇博客文章对应可以选择多个标签,而一个标签下也可以对应多篇发表的文章,这是典型的多对多关系,所以二者关系配置如下:

复制代码
@Entity
public class Post {

    public Post() {
    }

    public Post(String title) {
        this.title = title;
    }

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "post_tag",
            joinColumns = @JoinColumn(name = "post_id"),
            inverseJoinColumns = @JoinColumn(name = "tag_id")
    )
    private List<Tag> tags = new ArrayList<>();

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

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

    public void removeTag(Tag tag) {
        tags.remove(tag);
        tag.getPosts().remove(this);
    }
}
复制代码
复制代码
@Entity
public class Tag {
    @Id
    @GeneratedValue
    private Long id;

    @NaturalId
    private String name;

    @ManyToMany(mappedBy = "tags")
    private List<Post> posts = new ArrayList<>();

    public Tag() {
    }

    public Tag(String name) {
        this.name = name;
    }

    public List<Post> getPosts() {
        return posts;
    }

    public void setPosts(List<Post> posts) {
        this.posts = posts;
    }

    @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 Objects.hash(name);
    }
}
复制代码

多对多对关系通过生成第三张表即中间表来进行关联,同时我们也知道tag属于post,所以通过属性mappedBy来进行关联,tag实体具有唯一的业务键即name属性,该键用特定于Hibernate的@NaturalId注解,我们可通过唯一业务键来判断tag是否相等,所以我们重写了equals和hashCode方法,最终将生成如下表关联示意图:

接下来通过数据来进行测试,首先我们打开一个会话保存post和tag,然后再打开一个会话将已保存返回的某一个post的主键进行查询,最终实例化一个tag,通过查询出来的post中的tag集合移除实例化的tag,我们看看最终生成的SQL语句是否如我们所期望的那样:

复制代码
        Transaction transaction = null;

        Post post1 = new Post("JPA with Hibernate");
        Post post2 = new Post("Native Hibernate");

        Tag tag1 = new Tag("Java");
        Tag tag2 = new Tag("Hibernate");

        post1.addTag(tag1);
        post1.addTag(tag2);

        post2.addTag(tag1);

        try (Session session = HibernateUtil.getSessionFactory().openSession()) {

            transaction = session.beginTransaction();

            session.save(post1);
            session.save(post2);

            transaction.commit();

        } catch (Exception e) {
            if (transaction != null) {
                transaction.rollback();
            }
            e.printStackTrace();
        }

        try (Session session = HibernateUtil.getSessionFactory().openSession()) {

            transaction = session.beginTransaction();

            Tag newTag = new Tag("Java");

            Post querypost1 = session.find(Post.class, post1.getId());

            querypost1.removeTag(newTag);

            transaction.commit();

        } catch (Exception e) {
            if (transaction != null) {
                transaction.rollback();
            }
            e.printStackTrace();
        }
复制代码

最终生成的SQL语句如上,我们看到基于给定的post_id,然后将post_tag表中的所对应的post_id都已经删除,这点完全没毛病,但是最终又重新插入了一条,很显然,因为我们所实例化的tag处于未被Hibernate跟踪的状态,所以才有了先删除,然后再执行重新插入操作,在实际情况下我们可以认为这样操作没有什么很大意义,我们只是想移除在post_tag表中post_id所对应的数据,从数据库层面来看,这样操作毫无效率可言,因为数据库要做更多额外的工作,如重建索引。执行上述插入操作的问题出在对于目标实体所使用的集合类型,我们应该使用Set<>类型而不是List<>,接下来我们将实体post和tag中所对应的实体集合改造成如下:

    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "post_tag",
            joinColumns = @JoinColumn(name = "post_id"),
            inverseJoinColumns = @JoinColumn(name = "tag_id")
    )
    private Set<Tag> tags = new HashSet<>();
    @ManyToMany(mappedBy = "tags")
    private Set<Post> posts = new HashSet<>();

此时我们看到生成的SQL语句仅执行一条DELETE语句,该语句将删除关联的post_tag表数据,而没有了重新插入操作,完美解决问题。

总结

本节我们重点讲解了在Hibernate中的多对多关系映射要使用对目标实体和源实体集合要使用Set<>集合类型而非List<>集合类型,否则将可能会执行多余而不必要的操作,下一节我们开始进入到Hibernate中实体状态的详细讲解。


为了方便大家在移动端也能看到我分享的博文,现已注册个人公众号,扫描上方左边二维码即可,欢迎大家关注,有时间会及时分享相关技术博文。

感谢花时间阅读此篇文章,如果您觉得这篇文章你学到了东西也是为了犒劳下博主的码字不易不妨打赏一下吧,让楼主能喝上一杯咖啡,在此谢过了!
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!
本文版权归作者和博客园共有,来源网址:http://www.cnblogs.com/CreateMyself)/欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
posted @   Jeffcky  阅读(834)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
历史上的今天:
2017-03-08 SQL Server-聚焦NOLOCK、UPDLOCK、HOLDLOCK、READPAST你弄懂多少?(三十四)
点击右上角即可分享
微信分享提示