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