简体   繁体   English

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

[英]Spring-Data-JPA ManyToMany relationship with extra column

I've been struggling to make a many to many relationship with an additional column in the link table. 我一直在努力与链接表中的其他列建立多对多关系。

These are my entities: 这些是我的实体:

    @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);
    }
}

I have a post entity that is mapped to many tags and a tag that is mapped to many posts. 我有一个映射到许多标签的帖子实体和一个映射到许多帖子的标签。 The link table is PostTag which contains the mappings to both sides, and the additional column, "impact". 链接表是PostTag,其中包含双方的映射以及附加列“ impact”。 The PK of the link table is mapped to an Embeddable table PostTagId, which contains the PK from Post and Tag. 链接表的PK映射到可嵌入表PostTagId,该表包含Post和Tag的PK。

When I try to insert new entities, I do the following: 当我尝试插入新实体时,请执行以下操作:

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);

When trying to insert these items, I get the error that I cannot insert NULL into ("POST_TAG"."ID") 尝试插入这些项目时,出现无法将NULL插入的错误(“ POST_TAG”。“ ID”)

Anything that I've tried, it either comes with other errors, or it gets right back at it. 我尝试过的任何东西都可能带有其他错误,或者很快就恢复了。

Most probably something from the model is not right, but I really cannot figure what is wrong with it. 该模型中的某些内容很可能是不正确的,但我真的无法弄清楚它出了什么问题。

The whole modelling was based on this article The best way to ... 整个建模基于本文

Any help would be really appreciated. 任何帮助将非常感激。

Thanks 谢谢

The spring-data-jpa is a layer on top of JPA. spring-data-jpa是JPA之上的一层。 Each entity has its own repository and you have to deal with that. 每个实体都有其自己的存储库,您必须处理该存储库。 I've seen that tutorial mentioned above and it's for JPA and it's also setting ID's to null which seems off a bit and probably the cause of your error. 我已经看过上面提到的教程,它是针对JPA的,并且还将ID设置为null,这似乎有点不正确,可能是导致错误的原因。 I didn't look that close. 我没那么近看。 For dealing with the issue in spring-data-jpa you need a separate repository for the link table. 为了处理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);
    }

And to use it, as show above: 并使用它,如上所示:

@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);
}

Note there are few changes. 请注意,更改很少。 I don't explicitly set the PostTagId id's. 我没有明确设置PostTagId ID。 These are handled by the persistence layer (hibernate in this case). 这些由持久层(在这种情况下为休眠)处理。

Note also that you can update PostTag entries either explicity with its own repo or by adding and removing them from the list since CascadeType.ALL is set, as shown. 还请注意,由于设置了CascadeType.ALL因此可以使用其自己的存储库来显式更新PostTag条目,也可以通过从列表中添加和删除PostTag条目来进行显示,如图所示。 The problem with using the CascadeType.ALL for spring-data-jpa is that even though you prefetch the join table entities spring-data-jpa will do it again anyway. CascadeType.ALL用于spring-data-jpa的问题在于,即使您预取了联接表实体,spring-data-jpa仍将再次执行此操作。 Trying to update the relationship through the CascadeType.ALL for new entities is problematic. 尝试通过CascadeType.ALL为新实体更新关系是有问题的。

Without the CascadeType neither the posts or tags lists (which should be Sets) are the owners of the relationship so adding to them wouldn't accomplish anything in terms of persistence and would be for query results only. 如果没有CascadeTypepoststags列表(应为Set)都不是该关系的所有者,因此,在持久性方面添加到它们将不会完成任何事情,并且仅用于查询结果。

When reading the PostTag relationships you need to specifically fetch them since you don't have FetchType.EAGER . 在读取PostTag关系时,由于没有FetchType.EAGER需要专门获取它们。 The problem with FetchType.EAGER is the overhead if you don't want the joins and also if you put it on both Tag and Post then you will create a recursive fetch that gets all Tags and Posts for any query. 这个问题FetchType.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);

Finally, always check the logs. 最后,请始终检查日志。 Note that creating an association requires spring-data-jpa (and I think JPA) to read the existing table to see if the relationship is new or updated. 请注意,创建关联需要spring-data-jpa(我认为是JPA)读取现有表以查看该关系是新建的还是更新的。 This happens whether you create and save a PostTag yourself or even if you prefetched the list. 无论您自己创建和保存PostTag还是预取列表, PostTag发生这种情况。 JPA has a separate merge and I think you can use that more efficiently. 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 (?, ?)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM