Cascade.MERGE is finally tested successfully

There is a many-to-many relationship between User and Role. Just use Cascade.MERGE for cascade.

Role will have some initial data saved when the system starts, and can be added again. User can be bound to these Roles. Role will not be affected when User is deleted.

Below is the relevant code for the experiment, which should work.

bean / ForumPost.java

@Getter
@Setter
@ToString
@NoArgsConstructor
@Entity
@Table(name="forum_post")
public class ForumPost {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    @ManyToMany(cascade = {CascadeType.MERGE}, fetch = FetchType.EAGER)
    @JoinTable(
            name = "forum_post_tag",
            joinColumns = @JoinColumn(name = "post_id"),
            inverseJoinColumns = @JoinColumn(name = "tag_id")
    )
    private List<ForumTag> tags = new ArrayList<>();
}

bean/ForumTag.java

@Getter
@Setter
@ToString
@NoArgsConstructor
@Entity
@Table(name="forum_tag")
public class ForumTag {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

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

repository/ForumPostRepository.java

public interface ForumPostRepository extends JpaRepository<ForumPost, Long> {
    Optional<ForumPost> findByTitle(String title);

}

repository/ForumTagRepository.java

public interface ForumTagRepository extends JpaRepository<ForumTag, Long> {
    Optional<ForumTag> findByName(String name);
}

test/ForumTagRepositoryTest.java

@SpringBootTest
public class ForumTagRepositoryTest {

    @Autowired
    private ForumTagRepository tagRepository;

    @Autowired
    private ForumPostRepository postRepository;



    @Test
    void injectedComponentsAreNotNull(){
        assertNotNull(tagRepository);
        assertNotNull(postRepository);
    }


    private String tagName1 = "SpringBoot";
    private String tagName2 = "SwiftUI";
    private String tagName3 = "Java";
    private String postTitle1 = "Tutorial about Springboot";
    private String postTitle2 = "Tutorial about SwiftUI";
    private String postTitle3 = "Tutorial about Java";
    private ForumTag tag1 = new ForumTag(tagName1);
    private ForumTag tag2 = new ForumTag(tagName2);
    private ForumTag tag3 = new ForumTag(tagName3);



    private void detach() {
        // 1-detach
        if (!postRepository.findByTitle(postTitle1).isEmpty()) {
            ForumPost post1 = postRepository.findByTitle(postTitle1).get();
            post1.setTags(null);
            postRepository.save(post1);
        }
        if (!postRepository.findByTitle(postTitle2).isEmpty()) {
            ForumPost post2 = postRepository.findByTitle(postTitle2).get();
            post2.setTags(null);
            postRepository.save(post2);
        }
        if (!postRepository.findByTitle(postTitle3).isEmpty()) {
            ForumPost post3 = postRepository.findByTitle(postTitle3).get();
            post3.setTags(null);
            postRepository.save(post3);
        }
    }

    public void clearData() {
        // 1-detach
        detach();

        // 2- delete
        tagRepository.deleteAll();
        postRepository.deleteAll();
    }


    public void prepareData() {
        tagRepository.save(tag1);
        tagRepository.save(tag2);
        tagRepository.save(tag3);

        ForumPost post1 = new ForumPost();
        post1.setTitle(postTitle1);
        post1.setTags(Arrays.asList(tag1));

        ForumPost post2 = new ForumPost();
        post2.setTitle(postTitle2);
        post2.setTags(Arrays.asList(tag1, tag2));

        ForumPost post3 = new ForumPost();
        post3.setTitle(postTitle3);
        post3.setTags(Arrays.asList(tag1, tag2, tag3));

        postRepository.save(post1);
        postRepository.save(post2);
        postRepository.save(post3);
    }





    @Test
    public void test_save_tag_and_save_post() throws ResourceNotFoundException {

        clearData();

        // we have a few pre-saved tags
        tagRepository.save(tag1);
        tagRepository.save(tag2);
        tagRepository.save(tag3);

        assertEquals(tagRepository.findByName(tagName1).get().getName(), tagName1);
        assertEquals(tagRepository.findByName(tagName2).get().getName(), tagName2);
        assertEquals(tagRepository.findByName(tagName3).get().getName(), tagName3);

        // see if we can save these posts and connect with tags
        ForumPost post1 = new ForumPost();
        post1.setTitle(postTitle1);
        post1.setTags(Arrays.asList(tag1));

        ForumPost post2 = new ForumPost();
        post2.setTitle(postTitle2);
        post2.setTags(Arrays.asList(tag1, tag2));

        ForumPost post3 = new ForumPost();
        post3.setTitle(postTitle3);
        post3.setTags(Arrays.asList(tag1, tag2, tag3));

        postRepository.save(post1);
        postRepository.save(post2);
        postRepository.save(post3);

        assertEquals(postRepository.findByTitle(postTitle1).get().getTitle(), postTitle1);
        assertEquals(postRepository.findByTitle(postTitle1).get().getTags().size(), 1);
        assertEquals(postRepository.findByTitle(postTitle1).get().getTags().get(0).getName(), tagName1);


        assertEquals(postRepository.findByTitle(postTitle2).get().getTitle(), postTitle2);
        assertEquals(postRepository.findByTitle(postTitle2).get().getTags().size(), 2);
        assertEquals(postRepository.findByTitle(postTitle2).get().getTags().get(0).getName(), tagName1);
        assertEquals(postRepository.findByTitle(postTitle2).get().getTags().get(1).getName(), tagName2);


        assertEquals(postRepository.findByTitle(postTitle3).get().getTitle(), postTitle3);
        assertEquals(postRepository.findByTitle(postTitle3).get().getTags().size(), 3);
        assertEquals(postRepository.findByTitle(postTitle3).get().getTags().get(0).getName(), tagName1);
        assertEquals(postRepository.findByTitle(postTitle3).get().getTags().get(1).getName(), tagName2);
        assertEquals(postRepository.findByTitle(postTitle3).get().getTags().get(2).getName(), tagName3);
    }



    @Test
    public void test_save_post() throws ResourceNotFoundException {

        clearData();

        // let's save post directly to see how tags
        ForumPost post1 = new ForumPost();
        post1.setTitle(postTitle1);
        post1.setTags(Arrays.asList(tag1));

        ForumPost post2 = new ForumPost();
        post2.setTitle(postTitle2);
        post2.setTags(Arrays.asList(tag1, tag2));

        ForumPost post3 = new ForumPost();
        post3.setTitle(postTitle3);
        post3.setTags(Arrays.asList(tag1, tag2, tag3));


        Exception exception1 = assertThrows(InvalidDataAccessApiUsageException.class, () -> { postRepository.save(post1); });
        Exception exception2 = assertThrows(InvalidDataAccessApiUsageException.class, () -> { postRepository.save(post2); });
        Exception exception3 = assertThrows(InvalidDataAccessApiUsageException.class, () -> { postRepository.save(post3); });

        assertEquals("org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.example.spring_data_mysql.forum.bean.ForumTag",
                exception1.getMessage());
        assertEquals("org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.example.spring_data_mysql.forum.bean.ForumTag",
                exception2.getMessage());
        assertEquals("org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.example.spring_data_mysql.forum.bean.ForumTag",
                exception3.getMessage());

    }


    @Test
    public void test_delete_post_to_see_tag() {
        clearData();
        prepareData();

        // 1- delete post
        ForumPost post1 = postRepository.findByTitle(postTitle1).get();
        postRepository.deleteById(post1.getId());

        ForumPost post2 = postRepository.findByTitle(postTitle2).get();
        postRepository.deleteById(post2.getId());

        ForumPost post3 = postRepository.findByTitle(postTitle3).get();
        postRepository.deleteById(post3.getId());

        // 2- check tag
        assertEquals(tagRepository.findByName(tagName1).get().getName(), tagName1);
        assertEquals(tagRepository.findByName(tagName2).get().getName(), tagName2);
        assertEquals(tagRepository.findByName(tagName3).get().getName(), tagName3);
    }



    @Test
    public void test_delete_tag_to_see_post() {
        clearData();
        prepareData();

        // 1- delete tag
        ForumTag tag1 = tagRepository.findByName(tagName1).get();
        ForumTag tag2 = tagRepository.findByName(tagName2).get();
        ForumTag tag3 = tagRepository.findByName(tagName3).get();

        assertThrows(DataIntegrityViolationException.class, () -> { tagRepository.deleteById(tag1.getId()); });
        assertThrows(DataIntegrityViolationException.class, () -> { tagRepository.deleteById(tag2.getId()); });
        assertThrows(DataIntegrityViolationException.class, () -> { tagRepository.deleteById(tag3.getId()); });
    }


    @Test
    public void test_detach_post_then_delete_tag() {
        clearData();
        prepareData();
        detach();

        ForumTag tag1 = tagRepository.findByName(tagName1).get();
        tagRepository.deleteById(tag1.getId());

        ForumTag tag2 = tagRepository.findByName(tagName2).get();
        tagRepository.deleteById(tag2.getId());

        ForumTag tag3 = tagRepository.findByName(tagName3).get();
        tagRepository.deleteById(tag3.getId());

        // check post
        assertEquals(postRepository.findByTitle(postTitle1).get().getTitle(), postTitle1);
        assertEquals(postRepository.findByTitle(postTitle1).get().getTags().size(), 0);

        assertEquals(postRepository.findByTitle(postTitle2).get().getTitle(), postTitle2);
        assertEquals(postRepository.findByTitle(postTitle2).get().getTags().size(), 0);

        assertEquals(postRepository.findByTitle(postTitle3).get().getTitle(), postTitle3);
        assertEquals(postRepository.findByTitle(postTitle3).get().getTags().size(), 0);

    }
}

Regarding database testing, a real database is used. The following situations were tested

Test case Status
After the Tag is saved, is it possible? Saving Post Passed
Tag has no data. Will the Tag be saved when saving Post? No, because there is no Using Cascade.PERSIST
Is it possible to delete Post separately? Tag will not affect passing
Is it possible to delete the Tag separately? No, you need to contact the binding of the relevant Post first. Only when there is no binding, can you delete the Tag

The complete code is in this repository, https://github.com/tutehub/sample-spring/tree/develop