How to do unidirectional many-to-many relationships with already existing registers?












0















I'm new to Spring/JPA and I'm trying to create a relationship pretty much like this post from Vlad, but with one difference. My Tags already exist in another table.



So, if I do just like Vlad does in its post, creating a post, adding some tags to it and then persisting it, everything works as expected. I get a register on Post, two on Tag and two on PostTag.



Post newPost = new Post("Title");
newPost.addTag(new Tag("TagName"));
newPost.addTag(new Tag("TagName2"));
this.postRepository.save(newPost);


But, if I try to create a tag and save it before creating a post, I get an error.



Tag tag = new Tag("TagAlreadyCreated");
this.tagRepository.save(tag);
Post newPost = new Post("Title");
newPost.addTag(tag);
this.postRepository.save(newPost);
// Error: detached entity passed to persist: com.***.***.Tag


I get it that I don't want to create the Tag if it already exists and that the detached message means my Tag already has an ID, so I tried to change the CascadeType to MERGE, but then I don't get a register created on PostTag. Code for the classes:



Post



@Entity(name = "Post")
@Table(name = "post")
public class Post {

@Id
@GeneratedValue
private Long id;

private String title;

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

public Post() {
}

public Post(String title) {
this.title = title;
}

public void addTag(Tag tag) {
PostTag postTag = new PostTag(this, tag);
tags.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.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 Objects.equals(title, post.title);
}

@Override
public int hashCode() {
return Objects.hash(title);
}

public Long getId() {
return id;
}
}


Tag



@Entity(name = "Tag")
@Table(name = "tag")
@NaturalIdCache
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Tag {

@Id
@GeneratedValue
private Long id;

public Long getId() {
return id;
}

@NaturalId
private String name;

public Tag() {
}

public Tag(String name) {
this.name = name;
}

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


PostTag



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

@Column(name = "created_on")
private Date createdOn = new Date();

private PostTag() {}

public void setPost(Post post) {
this.post = post;
}

public void setTag(Tag tag) {
this.tag = tag;
}

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

//Getters and setters omitted for brevity

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

public Post getPost() {
return post;
}

public Tag getTag() {
return tag;
}

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


PostTagId



@Embeddable
public class PostTagId
implements Serializable {

@Column(name = "post_id")
private Long postId;

@Column(name = "tag_id")
private Long tagId;

private PostTagId() {}

public PostTagId(
Long postId,
Long tagId) {
this.postId = postId;
this.tagId = tagId;
}

//Getters omitted for brevity

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









share|improve this question





























    0















    I'm new to Spring/JPA and I'm trying to create a relationship pretty much like this post from Vlad, but with one difference. My Tags already exist in another table.



    So, if I do just like Vlad does in its post, creating a post, adding some tags to it and then persisting it, everything works as expected. I get a register on Post, two on Tag and two on PostTag.



    Post newPost = new Post("Title");
    newPost.addTag(new Tag("TagName"));
    newPost.addTag(new Tag("TagName2"));
    this.postRepository.save(newPost);


    But, if I try to create a tag and save it before creating a post, I get an error.



    Tag tag = new Tag("TagAlreadyCreated");
    this.tagRepository.save(tag);
    Post newPost = new Post("Title");
    newPost.addTag(tag);
    this.postRepository.save(newPost);
    // Error: detached entity passed to persist: com.***.***.Tag


    I get it that I don't want to create the Tag if it already exists and that the detached message means my Tag already has an ID, so I tried to change the CascadeType to MERGE, but then I don't get a register created on PostTag. Code for the classes:



    Post



    @Entity(name = "Post")
    @Table(name = "post")
    public class Post {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

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

    public Post() {
    }

    public Post(String title) {
    this.title = title;
    }

    public void addTag(Tag tag) {
    PostTag postTag = new PostTag(this, tag);
    tags.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.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 Objects.equals(title, post.title);
    }

    @Override
    public int hashCode() {
    return Objects.hash(title);
    }

    public Long getId() {
    return id;
    }
    }


    Tag



    @Entity(name = "Tag")
    @Table(name = "tag")
    @NaturalIdCache
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    public class Tag {

    @Id
    @GeneratedValue
    private Long id;

    public Long getId() {
    return id;
    }

    @NaturalId
    private String name;

    public Tag() {
    }

    public Tag(String name) {
    this.name = name;
    }

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


    PostTag



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

    @Column(name = "created_on")
    private Date createdOn = new Date();

    private PostTag() {}

    public void setPost(Post post) {
    this.post = post;
    }

    public void setTag(Tag tag) {
    this.tag = tag;
    }

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

    //Getters and setters omitted for brevity

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

    public Post getPost() {
    return post;
    }

    public Tag getTag() {
    return tag;
    }

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


    PostTagId



    @Embeddable
    public class PostTagId
    implements Serializable {

    @Column(name = "post_id")
    private Long postId;

    @Column(name = "tag_id")
    private Long tagId;

    private PostTagId() {}

    public PostTagId(
    Long postId,
    Long tagId) {
    this.postId = postId;
    this.tagId = tagId;
    }

    //Getters omitted for brevity

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









    share|improve this question



























      0












      0








      0


      1






      I'm new to Spring/JPA and I'm trying to create a relationship pretty much like this post from Vlad, but with one difference. My Tags already exist in another table.



      So, if I do just like Vlad does in its post, creating a post, adding some tags to it and then persisting it, everything works as expected. I get a register on Post, two on Tag and two on PostTag.



      Post newPost = new Post("Title");
      newPost.addTag(new Tag("TagName"));
      newPost.addTag(new Tag("TagName2"));
      this.postRepository.save(newPost);


      But, if I try to create a tag and save it before creating a post, I get an error.



      Tag tag = new Tag("TagAlreadyCreated");
      this.tagRepository.save(tag);
      Post newPost = new Post("Title");
      newPost.addTag(tag);
      this.postRepository.save(newPost);
      // Error: detached entity passed to persist: com.***.***.Tag


      I get it that I don't want to create the Tag if it already exists and that the detached message means my Tag already has an ID, so I tried to change the CascadeType to MERGE, but then I don't get a register created on PostTag. Code for the classes:



      Post



      @Entity(name = "Post")
      @Table(name = "post")
      public class Post {

      @Id
      @GeneratedValue
      private Long id;

      private String title;

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

      public Post() {
      }

      public Post(String title) {
      this.title = title;
      }

      public void addTag(Tag tag) {
      PostTag postTag = new PostTag(this, tag);
      tags.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.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 Objects.equals(title, post.title);
      }

      @Override
      public int hashCode() {
      return Objects.hash(title);
      }

      public Long getId() {
      return id;
      }
      }


      Tag



      @Entity(name = "Tag")
      @Table(name = "tag")
      @NaturalIdCache
      @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
      public class Tag {

      @Id
      @GeneratedValue
      private Long id;

      public Long getId() {
      return id;
      }

      @NaturalId
      private String name;

      public Tag() {
      }

      public Tag(String name) {
      this.name = name;
      }

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


      PostTag



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

      @Column(name = "created_on")
      private Date createdOn = new Date();

      private PostTag() {}

      public void setPost(Post post) {
      this.post = post;
      }

      public void setTag(Tag tag) {
      this.tag = tag;
      }

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

      //Getters and setters omitted for brevity

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

      public Post getPost() {
      return post;
      }

      public Tag getTag() {
      return tag;
      }

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


      PostTagId



      @Embeddable
      public class PostTagId
      implements Serializable {

      @Column(name = "post_id")
      private Long postId;

      @Column(name = "tag_id")
      private Long tagId;

      private PostTagId() {}

      public PostTagId(
      Long postId,
      Long tagId) {
      this.postId = postId;
      this.tagId = tagId;
      }

      //Getters omitted for brevity

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









      share|improve this question
















      I'm new to Spring/JPA and I'm trying to create a relationship pretty much like this post from Vlad, but with one difference. My Tags already exist in another table.



      So, if I do just like Vlad does in its post, creating a post, adding some tags to it and then persisting it, everything works as expected. I get a register on Post, two on Tag and two on PostTag.



      Post newPost = new Post("Title");
      newPost.addTag(new Tag("TagName"));
      newPost.addTag(new Tag("TagName2"));
      this.postRepository.save(newPost);


      But, if I try to create a tag and save it before creating a post, I get an error.



      Tag tag = new Tag("TagAlreadyCreated");
      this.tagRepository.save(tag);
      Post newPost = new Post("Title");
      newPost.addTag(tag);
      this.postRepository.save(newPost);
      // Error: detached entity passed to persist: com.***.***.Tag


      I get it that I don't want to create the Tag if it already exists and that the detached message means my Tag already has an ID, so I tried to change the CascadeType to MERGE, but then I don't get a register created on PostTag. Code for the classes:



      Post



      @Entity(name = "Post")
      @Table(name = "post")
      public class Post {

      @Id
      @GeneratedValue
      private Long id;

      private String title;

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

      public Post() {
      }

      public Post(String title) {
      this.title = title;
      }

      public void addTag(Tag tag) {
      PostTag postTag = new PostTag(this, tag);
      tags.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.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 Objects.equals(title, post.title);
      }

      @Override
      public int hashCode() {
      return Objects.hash(title);
      }

      public Long getId() {
      return id;
      }
      }


      Tag



      @Entity(name = "Tag")
      @Table(name = "tag")
      @NaturalIdCache
      @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
      public class Tag {

      @Id
      @GeneratedValue
      private Long id;

      public Long getId() {
      return id;
      }

      @NaturalId
      private String name;

      public Tag() {
      }

      public Tag(String name) {
      this.name = name;
      }

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


      PostTag



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

      @Column(name = "created_on")
      private Date createdOn = new Date();

      private PostTag() {}

      public void setPost(Post post) {
      this.post = post;
      }

      public void setTag(Tag tag) {
      this.tag = tag;
      }

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

      //Getters and setters omitted for brevity

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

      public Post getPost() {
      return post;
      }

      public Tag getTag() {
      return tag;
      }

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


      PostTagId



      @Embeddable
      public class PostTagId
      implements Serializable {

      @Column(name = "post_id")
      private Long postId;

      @Column(name = "tag_id")
      private Long tagId;

      private PostTagId() {}

      public PostTagId(
      Long postId,
      Long tagId) {
      this.postId = postId;
      this.tagId = tagId;
      }

      //Getters omitted for brevity

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






      java hibernate jpa spring-data-jpa






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Jan 1 at 17:44









      piotr szybicki

      6511510




      6511510










      asked Jan 1 at 17:29









      Lucas HeimLucas Heim

      112




      112
























          1 Answer
          1






          active

          oldest

          votes


















          0














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


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



          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.



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


          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 (?, ?)





          share|improve this answer
























          • Thanks for the explanation! I haven't found a way to see the logs yet, been searching for that, but anyway, are you saying there's no way to let JPA/Hibernate deal with association creation entries? Just to clarify, in my application I'll have a list of Tags created from the beggining and I just want to create posts and associate them with already existing tags.

            – Lucas Heim
            Jan 1 at 20:26











          • Well, you are using spring-data-jpa, not jpa. Even though it is a layer on top of jpa it is very abstracted from actual jpa. There is no reason I know of that you can't use existing tags but you have a set of entities that matches an existing answer so I just posted it here again. Sounds like you need to play around a bit more with the code but keep in mind spring-data-jpa has its own quirks that sometimes complicates these sorts of things.

            – K.Nicholas
            Jan 1 at 21:08











          Your Answer






          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "1"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53997499%2fhow-to-do-unidirectional-many-to-many-relationships-with-already-existing-regist%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          0














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


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



          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.



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


          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 (?, ?)





          share|improve this answer
























          • Thanks for the explanation! I haven't found a way to see the logs yet, been searching for that, but anyway, are you saying there's no way to let JPA/Hibernate deal with association creation entries? Just to clarify, in my application I'll have a list of Tags created from the beggining and I just want to create posts and associate them with already existing tags.

            – Lucas Heim
            Jan 1 at 20:26











          • Well, you are using spring-data-jpa, not jpa. Even though it is a layer on top of jpa it is very abstracted from actual jpa. There is no reason I know of that you can't use existing tags but you have a set of entities that matches an existing answer so I just posted it here again. Sounds like you need to play around a bit more with the code but keep in mind spring-data-jpa has its own quirks that sometimes complicates these sorts of things.

            – K.Nicholas
            Jan 1 at 21:08
















          0














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


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



          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.



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


          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 (?, ?)





          share|improve this answer
























          • Thanks for the explanation! I haven't found a way to see the logs yet, been searching for that, but anyway, are you saying there's no way to let JPA/Hibernate deal with association creation entries? Just to clarify, in my application I'll have a list of Tags created from the beggining and I just want to create posts and associate them with already existing tags.

            – Lucas Heim
            Jan 1 at 20:26











          • Well, you are using spring-data-jpa, not jpa. Even though it is a layer on top of jpa it is very abstracted from actual jpa. There is no reason I know of that you can't use existing tags but you have a set of entities that matches an existing answer so I just posted it here again. Sounds like you need to play around a bit more with the code but keep in mind spring-data-jpa has its own quirks that sometimes complicates these sorts of things.

            – K.Nicholas
            Jan 1 at 21:08














          0












          0








          0







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


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



          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.



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


          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 (?, ?)





          share|improve this answer













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


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



          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.



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


          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 (?, ?)






          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Jan 1 at 19:38









          K.NicholasK.Nicholas

          5,31932440




          5,31932440













          • Thanks for the explanation! I haven't found a way to see the logs yet, been searching for that, but anyway, are you saying there's no way to let JPA/Hibernate deal with association creation entries? Just to clarify, in my application I'll have a list of Tags created from the beggining and I just want to create posts and associate them with already existing tags.

            – Lucas Heim
            Jan 1 at 20:26











          • Well, you are using spring-data-jpa, not jpa. Even though it is a layer on top of jpa it is very abstracted from actual jpa. There is no reason I know of that you can't use existing tags but you have a set of entities that matches an existing answer so I just posted it here again. Sounds like you need to play around a bit more with the code but keep in mind spring-data-jpa has its own quirks that sometimes complicates these sorts of things.

            – K.Nicholas
            Jan 1 at 21:08



















          • Thanks for the explanation! I haven't found a way to see the logs yet, been searching for that, but anyway, are you saying there's no way to let JPA/Hibernate deal with association creation entries? Just to clarify, in my application I'll have a list of Tags created from the beggining and I just want to create posts and associate them with already existing tags.

            – Lucas Heim
            Jan 1 at 20:26











          • Well, you are using spring-data-jpa, not jpa. Even though it is a layer on top of jpa it is very abstracted from actual jpa. There is no reason I know of that you can't use existing tags but you have a set of entities that matches an existing answer so I just posted it here again. Sounds like you need to play around a bit more with the code but keep in mind spring-data-jpa has its own quirks that sometimes complicates these sorts of things.

            – K.Nicholas
            Jan 1 at 21:08

















          Thanks for the explanation! I haven't found a way to see the logs yet, been searching for that, but anyway, are you saying there's no way to let JPA/Hibernate deal with association creation entries? Just to clarify, in my application I'll have a list of Tags created from the beggining and I just want to create posts and associate them with already existing tags.

          – Lucas Heim
          Jan 1 at 20:26





          Thanks for the explanation! I haven't found a way to see the logs yet, been searching for that, but anyway, are you saying there's no way to let JPA/Hibernate deal with association creation entries? Just to clarify, in my application I'll have a list of Tags created from the beggining and I just want to create posts and associate them with already existing tags.

          – Lucas Heim
          Jan 1 at 20:26













          Well, you are using spring-data-jpa, not jpa. Even though it is a layer on top of jpa it is very abstracted from actual jpa. There is no reason I know of that you can't use existing tags but you have a set of entities that matches an existing answer so I just posted it here again. Sounds like you need to play around a bit more with the code but keep in mind spring-data-jpa has its own quirks that sometimes complicates these sorts of things.

          – K.Nicholas
          Jan 1 at 21:08





          Well, you are using spring-data-jpa, not jpa. Even though it is a layer on top of jpa it is very abstracted from actual jpa. There is no reason I know of that you can't use existing tags but you have a set of entities that matches an existing answer so I just posted it here again. Sounds like you need to play around a bit more with the code but keep in mind spring-data-jpa has its own quirks that sometimes complicates these sorts of things.

          – K.Nicholas
          Jan 1 at 21:08




















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Stack Overflow!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53997499%2fhow-to-do-unidirectional-many-to-many-relationships-with-already-existing-regist%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Monofisismo

          Angular Downloading a file using contenturl with Basic Authentication

          Olmecas