복붙노트

[SPRING] Spring-Data-JPA ManyToMany 관계와 추가 컬럼

SPRING

Spring-Data-JPA ManyToMany 관계와 추가 컬럼

나는 링크 테이블의 추가 열과의 많은 관계를 만들기 위해 고심하고있다.

이들은 내 실체입니다.

    @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는 게시 및 태그의 PK를 포함하는 Embeddable 테이블 PostTagId에 매핑됩니다.

새 엔티티를 삽입하려고하면 다음을 수행합니다.

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").

내가 시도한 것 중 하나라도 다른 오류가 발생하거나 곧바로 오류가 발생합니다.

아마 모델의 어떤 부분이 옳지는 않지만 실제로 잘못된 점을 파악할 수는 없습니다.

전체 모델링은이 기사를 기반으로했습니다. 가장 좋은 방법은 ...

어떤 도움이라도 대단히 감사 할 것입니다.

감사

해결법

  1. ==============================

    1.spring-data-jpa는 JPA의 맨 위에있는 레이어입니다. 각 엔티티는 자체 리포지토리를 가지고 있으며이를 처리해야합니다. 나는 위에서 언급 한 튜토리얼을 보았고 그것은 JPA를위한 것이었고 또한 ID가 null로 설정되어있어 조금 벗어난 것 같고 아마도 오류의 원인 일 수도 있습니다. 나는 그걸 가까이에서 보지 않았다. spring-data-jpa의 문제를 처리하려면 링크 테이블을위한 별도의 저장소가 필요합니다.

    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를 명시 적으로 설정하지 않았습니다. 이것들은 퍼시스턴스 계층 (이 경우에는 hibernate)에 의해 처리됩니다.

    또한 그림과 같이 CascadeType.ALL이 설정되어 있으므로 명시 적으로 자체 Repo로 PostTag 항목을 업데이트하거나 목록에서 추가하거나 제거하여 PostTag 항목을 업데이트 할 수 있습니다. spring-data-jpa에 CascadeType.ALL을 사용하는 문제는 조인 테이블 엔티티를 프리 페치하더라도 spring-data-jpa가 어쨌든 다시이를 수행한다는 것입니다. 새로운 엔티티에 대한 CascadeType.ALL을 통해 관계를 업데이트하려고하면 문제가 발생합니다.

    CascadeType을 사용하지 않으면 게시물 또는 태그 목록 (집합이어야 함)이 관계 소유자가 아니므로 지속성 측면에서 아무 것도 수행하지 않으며 쿼리 결과에만 해당됩니다.

    PostTag 관계를 읽을 때 FetchType.EAGER가 없으므로 특별히 가져와야합니다. 조인을 원하지 않으면 FetchType.EAGER의 문제점이 오버 헤드가되며 태그와 포스트 모두에 넣으면 모든 쿼리에 대해 태그와 포스트를 모두 가져 오는 재귀 적 페치가 생성됩니다.

    @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)가 기존 테이블을 읽어 관계가 새롭거나 업데이트되었는지 확인해야합니다. 이 작업은 직접 포스트 태그를 만들고 저장할 때 또는 목록을 프리 페치 한 경우에도 발생합니다. 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 (?, ?)
    
  2. from https://stackoverflow.com/questions/52648330/spring-data-jpa-manytomany-relationship-with-extra-column by cc-by-sa and MIT license