Home > Back-end >  How to avoid saving duplicates in @ManyToMany, but insert into mapping table?
How to avoid saving duplicates in @ManyToMany, but insert into mapping table?

Time:02-23

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

  1. Have a service (for example: PostTag findOrCreateTagByName(String)) that fetch existing PostTag by name and eventually create them. Thus returning existing PostTag.
  2. 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();
}
  • Related