Spring-Data-JPA ManyToMany 与额外列的关系

Posted

技术标签:

【中文标题】Spring-Data-JPA ManyToMany 与额外列的关系【英文标题】:Spring-Data-JPA ManyToMany relationship with extra column 【发布时间】:2019-03-09 22:34:51 【问题描述】:

我一直在努力与链接表中的附加列建立多对多关系。

这些是我的实体:

    @JsonIgnoreProperties( "hibernateLazyInitializer", "handler" )
    public class Post 

    @Id
    @GeneratedValue
    private Long id;

    private String name; 

    @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
    @JsonIgnore
    private List<PostTag> tags = new ArrayList<>();

    //getters and setters

    public void addTag(Tag tag)
        PostTag postTag = new PostTag(this, tag);
        tags.add(postTag);
        tag.getPosts().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.getTag().getPosts().remove(postTag);
                postTag.setPost(null);
                postTag.setTag(null);
            
        
    

    @Override
    public boolean equals(Object o) 
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        Post post = (Post) o;
        return id == post.id;
    

    @Override
    public int hashCode() 
        return Objects.hash(id);
       
    

    @JsonIgnoreProperties( "hibernateLazyInitializer", "handler" )
    public class Tag 

    @Id
    @GeneratedValue
    private Long id;

    private String comment;

    @OneToMany(mappedBy = "tag", cascade = CascadeType.ALL, orphanRemoval = true)
    @JsonIgnore
    private List<PostTag> posts = new ArrayList<>();

    //getters and setters

    @Override
    public boolean equals(Object o) 
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        Tag that = (Tag) o;
        return id == that.id;
    

    @Override
    public int hashCode() 
        return Objects.hash(id);
    


@Entity(name = "PostTag")
@Table(name = "post_tag")
@JsonIgnoreProperties( "hibernateLazyInitializer", "handler" )
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;

    private Integer impact;

    public FacilityParticipant(Post post, Tag tag) 
        this.post = post;
        this.tag = tag;
        this.id = new PostTagId(post.getId(), tag.getId());
    

    //getters and setters

    @Override
    public boolean equals(Object o) 
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        PostTag that = (PostTag) o;
        return Objects.equals(post, that.post) && Objects.equals(tag, that.tag);
    

    @Override
    public int hashCode() 
        return Objects.hash(post, tag);
    


@Embeddable
public class PostTagId implements Serializable 

    private Long postId;

    private Long tagId;

    //getters setters

    @Override
    public boolean equals(Object o) 
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        PostTagId that = (PostTagId) o;
        return Objects.equals(postId, that.postId) && Objects.equals(tagId, that.tagId);
    

    @Override
    public int hashCode() 
        return Objects.hash(postId, tagId);
    

我有一个映射到许多标签的帖子实体和一个映射到许多帖子的标签。链接表是 PostTag,其中包含到双方的映射,以及附加列“影响”。链接表的 PK 映射到一个 Embeddable 表 PostTagId,其中包含来自 Post 和 Tag 的 PK。

当我尝试插入新实体时,我会执行以下操作:

Tag tag1 = new Tag();
Tag tag2 = new Tag();

repository.save(tag1);
repository.save(tag2);

Post post1 = new Post();
Post post2 = new Post();

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

post2.addTag(tag1);

repository.save(post1);
repository.save(post2);

当尝试插入这些项目时,我收到了错误,我 cannot insert NULL into ("POST_TAG"."ID")

我尝试过的任何东西,要么带有其他错误,要么立即返回。

很可能模型中的某些内容不正确,但我真的无法弄清楚它有什么问题。

整个建模都是基于这篇文章The best way to ...

任何帮助将不胜感激。

谢谢

【问题讨论】:

也许您错过了 PostTag 实体上的 @IdClass(PostTagId.class)? 没有它应该可以工作 这里的最后一个解决方案怎么样:***.com/questions/15130494/… 还是不行:( 【参考方案1】:

spring-data-jpa 是 JPA 之上的一层。每个实体都有自己的存储库,您必须处理它。我已经看过上面提到的那个教程,它是针对 JPA 的,它还将 ID 设置为 null,这看起来有点不对劲,可能是你的错误的原因。我没看那么近。为了处理 spring-data-jpa 中的问题,您需要一个单独的链接表存储库。

@Entity
public class Post 
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<PostTag> tags;

@Entity
public class Tag 
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    @OneToMany(mappedBy = "tag", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<PostTag> posts;

@Entity
public class PostTag 
    @EmbeddedId
    private PostTagId id = new PostTagId();

    @ManyToOne(fetch = FetchType.LAZY)
    @MapsId("postId")
    private Post post;

    @ManyToOne(fetch = FetchType.LAZY)
    @MapsId("tagId")
    private Tag tag;

    public PostTag() 
    public PostTag(Post post, Tag tag) 
        this.post = post;
        this.tag = tag;
    

@SuppressWarnings("serial")
@Embeddable
public class PostTagId implements Serializable 
    private Long postId;
    private Long tagId;
    @Override
    public boolean equals(Object o) 
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        PostTagId that = (PostTagId) o;
        return Objects.equals(postId, that.postId) && Objects.equals(tagId, that.tagId);
    
    @Override
    public int hashCode() 
        return Objects.hash(postId, tagId);
    

并使用它,如上所示:

@Transactional
private void update() 
    System.out.println("Step 1");
    Tag tag1 = new Tag();
    Post post1 = new Post();
    PostTag p1t1 = new PostTag(post1, tag1);
    tagRepo.save(tag1);
    postRepo.save(post1);
    postTagRepo.save(p1t1);

    System.out.println("Step 2");
    Tag tag2 = new Tag();
    Post post2 = new Post();
    PostTag p2t2 = new PostTag(post2, tag2);
    postRepo.save(post2);
    tagRepo.save(tag2);
    postTagRepo.save(p2t2);

    System.out.println("Step 3");
    tag2 = tagRepo.getOneWithPosts(2L);
    tag2.getPosts().add(new PostTag(post1, tag2));
    tagRepo.save(tag2);

    System.out.println("Step 4 -- better");
    PostTag p2t1 = new PostTag(post2, tag1);
    postTagRepo.save(p2t1);

请注意,更改很少。我没有明确设置PostTagId id。这些由持久层处理(在本例中为休眠)。

还请注意,您可以使用自己的 repo 或通过在列表中添加和删除它们来更新 PostTag 条目,因为设置了 CascadeType.ALL,如图所示。将CascadeType.ALL 用于spring-data-jpa 的问题在于,即使您预取连接表实体,spring-data-jpa 仍然会再次执行此操作。尝试通过 CascadeType.ALL 为新实体更新关系是有问题的。

没有CascadeTypepoststags 列表(应该是集合)都不是关系的所有者,因此添加到它们不会在持久性方面完成任何事情,并且仅用于查询结果.

读取PostTag 关系时,您需要专门获取它们,因为您没有FetchType.EAGERFetchType.EAGER 的问题是如果你不想要连接的开销,如果你把它放在 TagPost 上,那么你将创建一个递归获取,获取所有 TagsPosts任何查询。

@Query("select t from Tag t left outer join fetch t.posts tps left outer join fetch tps.post where t.id = :id")
Tag getOneWithPosts(@Param("id") Long id);

最后,始终检查日志。请注意,创建关联需要 spring-data-jpa(我认为是 JPA)来读取现有表以查看关系是新的还是更新的。无论您是自己创建和保存PostTag,还是即使您预取了列表,都会发生这种情况。 JPA 有一个单独的合并,我认为您可以更有效地使用它。

create table post (id bigint generated by default as identity, primary key (id))
create table post_tag (post_id bigint not null, tag_id bigint not null, primary key (post_id, tag_id))
create table tag (id bigint generated by default as identity, primary key (id))
alter table post_tag add constraint FKc2auetuvsec0k566l0eyvr9cs foreign key (post_id) references post
alter table post_tag add constraint FKac1wdchd2pnur3fl225obmlg0 foreign key (tag_id) references tag

Step 1
insert into tag (id) values (null)
insert into post (id) values (null)
select posttag0_.post_id as post_id1_1_0_, posttag0_.tag_id as tag_id2_1_0_ from post_tag posttag0_ where posttag0_.post_id=? and posttag0_.tag_id=?
insert into post_tag (post_id, tag_id) values (?, ?)

Step 2
insert into post (id) values (null)
insert into tag (id) values (null)
select posttag0_.post_id as post_id1_1_0_, posttag0_.tag_id as tag_id2_1_0_ from post_tag posttag0_ where posttag0_.post_id=? and posttag0_.tag_id=?
insert into post_tag (post_id, tag_id) values (?, ?)

Step 3
select tag0_.id as id1_2_0_, posts1_.post_id as post_id1_1_1_, posts1_.tag_id as tag_id2_1_1_, post2_.id as id1_0_2_, posts1_.tag_id as tag_id2_1_0__, posts1_.post_id as post_id1_1_0__ from tag tag0_ left outer join post_tag posts1_ on tag0_.id=posts1_.tag_id left outer join post post2_ on posts1_.post_id=post2_.id where tag0_.id=?
select tag0_.id as id1_2_1_, posts1_.tag_id as tag_id2_1_3_, posts1_.post_id as post_id1_1_3_, posts1_.post_id as post_id1_1_0_, posts1_.tag_id as tag_id2_1_0_ from tag tag0_ left outer join post_tag posts1_ on tag0_.id=posts1_.tag_id where tag0_.id=?
select posttag0_.post_id as post_id1_1_0_, posttag0_.tag_id as tag_id2_1_0_ from post_tag posttag0_ where posttag0_.post_id=? and posttag0_.tag_id=?
insert into post_tag (post_id, tag_id) values (?, ?)

Step 4 -- better
select posttag0_.post_id as post_id1_1_0_, posttag0_.tag_id as tag_id2_1_0_ from post_tag posttag0_ where posttag0_.post_id=? and posttag0_.tag_id=?
insert into post_tag (post_id, tag_id) values (?, ?)

【讨论】:

以上是关于Spring-Data-JPA ManyToMany 与额外列的关系的主要内容,如果未能解决你的问题,请参考以下文章

Spring-data-jpa:批量插入不起作用

spring-data-jpa 1.11.16 带游标的存储过程

Spring-data-jpa详解

Spring-data-jpa 学习笔记

Spring-data-jpa + Hibernate 未创建预期表

SpringBoot整合spring-data-jpa