• 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中实体状态的详细讲解。

  • 相关阅读:
    事务之三:编程式事务、声明式事务(XML配置事务、注解实现事务)
    file的getPath getAbsolutePath和getCanonicalPath的不同
    处理 JSON null 和空数组及对象
    Eclipse快捷键大全(转载)
    Annotation之三:自定义注解示例,利用反射进行解析
    Annotation之二:@Inherited注解继承情况
    innodb事务日志详解
    事务之二:spring事务(事务管理方式,事务5隔离级别,7个事务传播行为,spring事务回滚条件)
    Java 数组的三种创建方法,数组拷贝方法
    Eclipse 远程调试
  • 原文地址:https://www.cnblogs.com/CreateMyself/p/12437433.html
Copyright © 2020-2023  润新知