Chinese version
User和Role之间是多对多的关系,cascade选用Cascade.MERGE就行。
Role会有些初始数据在系统启动时存进去,也可以再添加。User可以和这些Role绑定关系。User删除时不会影响Role。
下面是实验的相关代码,应该可以运行。
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);
}
}
关于数据库测试讲下,是用真实数据库。测试了如下情况
Test case | Status |
---|---|
Tag存好后,是否可以存Post | 通过 |
Tag没有数据,存Post时是否会把Tag也存进去 | 不会,因为没有使用Cascade.PERSIST |
是否可以单独删除Post,Tag不会影响 | 通过 |
是否可以单独删除Tag | 不可以,要先接触相关Post的绑定,没有任何绑定了,才可以删除这个Tag |
完整代码在这个仓库,https://github.com/tutehub/sample-spring/tree/develop