I have 2 entites Post and PostTag with many-to-many relationship. Also I have table post_tag_mapping with post_id and post_tag_id.
@ManyToMany(cascade = {CascadeType.ALL})
@JoinTable(
name = "post_tag_mapping",
joinColumns = @JoinColumn(name = "post_id"),
inverseJoinColumns = @JoinColumn(name = "tag_id")
)
@Getter
@Builder.Default
private Set<PostTag> postTagSet = new HashSet<>();
If I create Post with Set<PostTag>
- I save post, 1 or more post_tag, and table post_tag_mapping like post_id tag_id - (1, 1), (1, 2), (1, 3), etc.
But if I save post with post_tag name that already exists in database - I want to not save it to post_tag(I have unique index on post_tag.name), but create new post_tag_mapping for another post.
Now I get exception SQLIntegrityConstraintViolationException: Duplicate entry 'tag1' for key 'post_tag.idx_post_tag_name'
Don't really understand how to implement it.
CodePudding user response:
If I understand well your predicament, your problem is that you are trying to insert new PostTag
when saving your Post
entity.
Since you are doing a cascade save due to CascadeType.ALL, your EntityManager is roughly doing this:
- Saving Post
- Saving PostTag which cause your exception
- Saving Post <-> PostTag
You should
- Have a service (for example:
PostTag findOrCreateTagByName(String)
) that fetch existingPostTag
by name and eventually create them. Thus returning existingPostTag
. - Save the
Post
after with the association with said existing tags.
CodePudding user response:
Just do it.
@SpringBootApplication
public class DemoApplication implements ApplicationRunner{
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Autowired
PostRepository postRepository;
@Autowired
PostTagRepository postTagRepository;
@Override
public void run(ApplicationArguments args) throws Exception {
init();
testException("name");
addNewPost("name");
addNewPost("other");
readPosts();
}
private void init() {
postTagRepository.save(PostTag.builder().name("name").build());
}
private void testException(String name) {
try {
PostTag postTag = postTagRepository.save(PostTag.builder().name(name).build());
postRepository.save(Post.builder().tags(Collections.singleton(postTag)).build());
} catch ( DataIntegrityViolationException ex ) {
System.out.println("EX: " ex.getLocalizedMessage());
}
}
private void addNewPost(String name) {
PostTag postTag = postTagRepository.findByName(name)
.orElseGet(()->postTagRepository.save(PostTag.builder().name(name).build()));
postRepository.save(Post.builder().tags(Collections.singleton(postTag)).build());
}
private void readPosts() {
System.out.println(postRepository.findAll());
}
}
And don't use things you don't understand
@Entity
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Post {
@Id @GeneratedValue
private Long id;
@ManyToMany
private Set<PostTag> tags;
}
And get grammerly.
And handle eager fetch in the repo.
@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
@EntityGraph(attributePaths = { "tags" })
List<Post> findAll();
}