Spring-Data-JPA ManyToMany 与额外列的关系 [英] Spring-Data-JPA ManyToMany relationship with extra column

查看:25
本文介绍了Spring-Data-JPA ManyToMany 与额外列的关系的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I've been struggling to make a many to many relationship with an additional column in the link table.

这些是我的实体:

    @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.

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". The PK of the link table is mapped to an Embeddable table PostTagId, which contains the PK from Post and Tag.

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

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

尝试插入这些项目时,我收到错误:我无法将 NULL 插入 ("POST_TAG"."ID")

When trying to insert these items, I get the error that I cannot insert NULL into ("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.

整个建模基于这篇文章最好的方法......

任何帮助将不胜感激.

谢谢

推荐答案

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

The spring-data-jpa is a layer on top of 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. I didn't look that close. For dealing with the issue in spring-data-jpa you need a separate repository for the link table.

@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.这些由持久层(在本例中为休眠)处理.

Note there are few changes. I don't explicitly set the PostTagId id's. These are handled by the persistence layer (hibernate in this case).

另请注意,由于 CascadeType.ALL 已设置,您可以使用自己的存储库显式更新 PostTag 条目,也可以通过从列表中添加和删除它们来更新条目,如图所示.将 CascadeType.ALL 用于 spring-data-jpa 的问题在于,即使您预取连接表实体 spring-data-jpa 无论如何都会再次执行.尝试通过 CascadeType.ALL 为新实体更新关系是有问题的.

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. 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. Trying to update the relationship through the CascadeType.ALL for new entities is problematic.

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

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.

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

When reading the PostTag relationships you need to specifically fetch them since you don't have 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.

@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 有一个单独的合并,我认为您可以更有效地使用它.

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. This happens whether you create and save a PostTag yourself or even if you prefetched the list. JPA has a separate merge and I think you can use that more efficiently.

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 与额外列的关系的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆