From b2f92bce02ab4cd1e7d2479ed3b6535448026f9c Mon Sep 17 00:00:00 2001 From: apeterlic Date: Sat, 25 Mar 2023 17:43:25 +0100 Subject: [PATCH] BAEL-6189 - Remove Entity with ManyToMany Relationship in JPA (#13634) * BAEL-6189 - Remove Entity with ManyToMany Relationship in JPA * Add property source --- persistence-modules/spring-jpa-2/pom.xml | 2 +- .../manytomanyremoval/Application.java | 12 +++ .../baeldung/manytomanyremoval/Author.java | 62 +++++++++++++++ .../com/baeldung/manytomanyremoval/Book.java | 65 ++++++++++++++++ .../baeldung/manytomanyremoval/Category.java | 41 ++++++++++ .../com/baeldung/manytomanyremoval/Post.java | 59 ++++++++++++++ .../BidirectionalRemovalUnitTest.java | 78 +++++++++++++++++++ .../manytomanyremoval/TestContextConfig.java | 12 +++ .../UnidirectionalRemovalUnitTest.java | 55 +++++++++++++ .../manytomanyremoval/test.properties | 11 +++ 10 files changed, 396 insertions(+), 1 deletion(-) create mode 100644 persistence-modules/spring-jpa-2/src/main/java/com/baeldung/manytomanyremoval/Application.java create mode 100644 persistence-modules/spring-jpa-2/src/main/java/com/baeldung/manytomanyremoval/Author.java create mode 100644 persistence-modules/spring-jpa-2/src/main/java/com/baeldung/manytomanyremoval/Book.java create mode 100644 persistence-modules/spring-jpa-2/src/main/java/com/baeldung/manytomanyremoval/Category.java create mode 100644 persistence-modules/spring-jpa-2/src/main/java/com/baeldung/manytomanyremoval/Post.java create mode 100644 persistence-modules/spring-jpa-2/src/test/java/com/baeldung/manytomanyremoval/BidirectionalRemovalUnitTest.java create mode 100644 persistence-modules/spring-jpa-2/src/test/java/com/baeldung/manytomanyremoval/TestContextConfig.java create mode 100644 persistence-modules/spring-jpa-2/src/test/java/com/baeldung/manytomanyremoval/UnidirectionalRemovalUnitTest.java create mode 100644 persistence-modules/spring-jpa-2/src/test/resources/manytomanyremoval/test.properties diff --git a/persistence-modules/spring-jpa-2/pom.xml b/persistence-modules/spring-jpa-2/pom.xml index 19bc9cba14..974dd41df9 100644 --- a/persistence-modules/spring-jpa-2/pom.xml +++ b/persistence-modules/spring-jpa-2/pom.xml @@ -101,7 +101,7 @@ - 5.1.5.RELEASE + 5.2.0.RELEASE 2.2.6.RELEASE 9.0.0.M26 diff --git a/persistence-modules/spring-jpa-2/src/main/java/com/baeldung/manytomanyremoval/Application.java b/persistence-modules/spring-jpa-2/src/main/java/com/baeldung/manytomanyremoval/Application.java new file mode 100644 index 0000000000..7b8551ee0c --- /dev/null +++ b/persistence-modules/spring-jpa-2/src/main/java/com/baeldung/manytomanyremoval/Application.java @@ -0,0 +1,12 @@ +package com.baeldung.manytomanyremoval; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/persistence-modules/spring-jpa-2/src/main/java/com/baeldung/manytomanyremoval/Author.java b/persistence-modules/spring-jpa-2/src/main/java/com/baeldung/manytomanyremoval/Author.java new file mode 100644 index 0000000000..d9906a21cd --- /dev/null +++ b/persistence-modules/spring-jpa-2/src/main/java/com/baeldung/manytomanyremoval/Author.java @@ -0,0 +1,62 @@ +package com.baeldung.manytomanyremoval; + +import javax.persistence.*; +import java.util.HashSet; +import java.util.Set; + +@Entity +@Table(name = "author") +public class Author { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "name") + private String name; + + @ManyToMany(mappedBy = "authors") + private Set books = new HashSet<>(); + + // standard getters and setters + + public Author() { + } + + public Author(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getBooks() { + return books; + } + + public void setBooks(Set books) { + this.books = books; + } + + @PreRemove + private void removeBookAssociations() { + for (Book book : this.books) { + book.getAuthors().remove(this); + } + } + +} diff --git a/persistence-modules/spring-jpa-2/src/main/java/com/baeldung/manytomanyremoval/Book.java b/persistence-modules/spring-jpa-2/src/main/java/com/baeldung/manytomanyremoval/Book.java new file mode 100644 index 0000000000..4b49acd000 --- /dev/null +++ b/persistence-modules/spring-jpa-2/src/main/java/com/baeldung/manytomanyremoval/Book.java @@ -0,0 +1,65 @@ +package com.baeldung.manytomanyremoval; + + +import javax.persistence.*; +import java.util.HashSet; +import java.util.Set; + +@Entity +@Table(name = "book") +public class Book { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "title") + private String title; + + @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) + @JoinTable(name = "book_author", + joinColumns = @JoinColumn(name = "book_id"), + inverseJoinColumns = @JoinColumn(name = "author_id") + ) + private Set authors = new HashSet<>(); + + // standard getters and setters + + public Book() { + } + + public Book(String title) { + this.title = title; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Set getAuthors() { + return authors; + } + + public void removeAuthor(Author author) { + this.authors.remove(author); + author.getBooks().remove(this); + } + + public void addAuthor(Author author) { + authors.add(author); + author.getBooks().add(this); + } + +} diff --git a/persistence-modules/spring-jpa-2/src/main/java/com/baeldung/manytomanyremoval/Category.java b/persistence-modules/spring-jpa-2/src/main/java/com/baeldung/manytomanyremoval/Category.java new file mode 100644 index 0000000000..f7202f544c --- /dev/null +++ b/persistence-modules/spring-jpa-2/src/main/java/com/baeldung/manytomanyremoval/Category.java @@ -0,0 +1,41 @@ +package com.baeldung.manytomanyremoval; + +import javax.persistence.*; + +@Entity +@Table(name = "category") +public class Category { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "name") + private String name; + + // getters and setters + + public Category() { + } + + public Category(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/persistence-modules/spring-jpa-2/src/main/java/com/baeldung/manytomanyremoval/Post.java b/persistence-modules/spring-jpa-2/src/main/java/com/baeldung/manytomanyremoval/Post.java new file mode 100644 index 0000000000..d229a9d0d7 --- /dev/null +++ b/persistence-modules/spring-jpa-2/src/main/java/com/baeldung/manytomanyremoval/Post.java @@ -0,0 +1,59 @@ +package com.baeldung.manytomanyremoval; + + +import javax.persistence.*; +import java.util.HashSet; +import java.util.Set; + +@Entity +@Table(name = "post") +public class Post { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "title") + private String title; + + @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH}) + @JoinTable(name = "post_category", + joinColumns = @JoinColumn(name = "post_id"), + inverseJoinColumns = @JoinColumn(name = "category_id") + ) + private Set categories = new HashSet<>(); + + // getters and setters + + public Post(String title) { + this.title = title; + } + + public Post() { + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Set getCategories() { + return categories; + } + + public void setCategories(Set categories) { + this.categories = categories; + } +} diff --git a/persistence-modules/spring-jpa-2/src/test/java/com/baeldung/manytomanyremoval/BidirectionalRemovalUnitTest.java b/persistence-modules/spring-jpa-2/src/test/java/com/baeldung/manytomanyremoval/BidirectionalRemovalUnitTest.java new file mode 100644 index 0000000000..f214fcdc2a --- /dev/null +++ b/persistence-modules/spring-jpa-2/src/test/java/com/baeldung/manytomanyremoval/BidirectionalRemovalUnitTest.java @@ -0,0 +1,78 @@ +package com.baeldung.manytomanyremoval; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.transaction.annotation.Transactional; + +import javax.persistence.EntityManager; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = TestContextConfig.class) +@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) +@Transactional +class BidirectionalRemovalUnitTest { + + @Autowired + private EntityManager entityManager; + + @BeforeEach + void init() { + Author author1 = new Author("Erich Gamma"); + Author author2 = new Author("John Vlissides"); + Author author3 = new Author("Ralph Johnson"); + Author author4 = new Author("Richard Helm"); + + Book book = new Book(); + book.setTitle("GOF Java"); + book.addAuthor(author1); + book.addAuthor(author2); + book.addAuthor(author3); + book.addAuthor(author4); + entityManager.persist(book); + + Book book2 = new Book(); + book2.setTitle("A Lost Age"); + book2.addAuthor(author3); + entityManager.persist(book2); + } + + @Test + void givenEntities_whenRemoveFromOwner_thenRemoveAssociation() { + Author author = (Author) entityManager + .createQuery("SELECT author from Author author where author.name = ?1") + .setParameter(1, "Ralph Johnson") + .getSingleResult(); + + Book book1 = entityManager.find(Book.class, 1L); + Book book2 = entityManager.find(Book.class, 2L); + + book1.removeAuthor(author); + entityManager.persist(book1); + + Assertions.assertEquals(3, book1.getAuthors().size()); + Assertions.assertEquals(1, book2.getAuthors().size()); + } + + @Transactional + @Test + void givenEntities_whenRemoveFromNotOwner_thenRemoveAssociation() { + Author author = (Author) entityManager + .createQuery("SELECT author from Author author where author.name = ?1") + .setParameter(1, "Ralph Johnson") + .getSingleResult(); + Book book1 = entityManager.find(Book.class, 1L); + Book book2 = entityManager.find(Book.class, 2L); + + entityManager.remove(author); + + Assertions.assertEquals(3, book1.getAuthors().size()); + Assertions.assertEquals(0, book2.getAuthors().size()); + } +} + diff --git a/persistence-modules/spring-jpa-2/src/test/java/com/baeldung/manytomanyremoval/TestContextConfig.java b/persistence-modules/spring-jpa-2/src/test/java/com/baeldung/manytomanyremoval/TestContextConfig.java new file mode 100644 index 0000000000..6a840824de --- /dev/null +++ b/persistence-modules/spring-jpa-2/src/test/java/com/baeldung/manytomanyremoval/TestContextConfig.java @@ -0,0 +1,12 @@ +package com.baeldung.manytomanyremoval; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +@Configuration +@PropertySource("classpath:/manytomanyremoval/test.properties") +@ComponentScan("com.baeldung.manytomanyremoval") +public class TestContextConfig { + +} diff --git a/persistence-modules/spring-jpa-2/src/test/java/com/baeldung/manytomanyremoval/UnidirectionalRemovalUnitTest.java b/persistence-modules/spring-jpa-2/src/test/java/com/baeldung/manytomanyremoval/UnidirectionalRemovalUnitTest.java new file mode 100644 index 0000000000..b23b589fb7 --- /dev/null +++ b/persistence-modules/spring-jpa-2/src/test/java/com/baeldung/manytomanyremoval/UnidirectionalRemovalUnitTest.java @@ -0,0 +1,55 @@ +package com.baeldung.manytomanyremoval; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.transaction.annotation.Transactional; + +import javax.persistence.EntityManager; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = TestContextConfig.class) +@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) +@Transactional +class UnidirectionalRemovalUnitTest { + + @Autowired + private EntityManager entityManager; + + @BeforeEach + void init() { + Category category1 = new Category("JPA"); + Category category2 = new Category("Persistence"); + + Post post1 = new Post("Many-to-Many Relationship"); + post1.getCategories().add(category1); + post1.getCategories().add(category2); + + Post post2 = new Post("Entity Manager"); + post2.getCategories().add(category1); + + entityManager.persist(post1); + entityManager.persist(post2); + + Assertions.assertEquals(2, post1.getCategories().size()); + Assertions.assertEquals(1, post2.getCategories().size()); + } + + @Test + void givenEntities_whenRemove_thenRemoveAssociation() { + Post post1 = entityManager.find(Post.class, 1L); + Post post2 = entityManager.find(Post.class, 2L); + Category category = entityManager.find(Category.class, 1L); + + post1.getCategories().remove(category); + + Assertions.assertEquals(1, post1.getCategories().size()); + Assertions.assertEquals(1, post2.getCategories().size()); + } + +} diff --git a/persistence-modules/spring-jpa-2/src/test/resources/manytomanyremoval/test.properties b/persistence-modules/spring-jpa-2/src/test/resources/manytomanyremoval/test.properties new file mode 100644 index 0000000000..83479cd453 --- /dev/null +++ b/persistence-modules/spring-jpa-2/src/test/resources/manytomanyremoval/test.properties @@ -0,0 +1,11 @@ +spring.datasource.url=jdbc:h2:mem:mydb;MODE=LEGACY; +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= + +hibernate.dialect=org.hibernate.dialect.H2Dialect +hibernate.hbm2ddl.auto=none +spring.jpa.show-sql=true +spring.jpa.hibernate.dialect=org.hibernate.dialect.H2Dialect +spring.jpa.hibernate.ddl-auto=create-drop +