BAEL-6189 - Remove Entity with ManyToMany Relationship in JPA (#13634)
* BAEL-6189 - Remove Entity with ManyToMany Relationship in JPA * Add property source
This commit is contained in:
parent
211e5fbbe2
commit
b2f92bce02
|
@ -101,7 +101,7 @@
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<!-- Spring -->
|
<!-- Spring -->
|
||||||
<org.springframework.version>5.1.5.RELEASE</org.springframework.version>
|
<org.springframework.version>5.2.0.RELEASE</org.springframework.version>
|
||||||
<spring-boot.version>2.2.6.RELEASE</spring-boot.version>
|
<spring-boot.version>2.2.6.RELEASE</spring-boot.version>
|
||||||
<!-- persistence -->
|
<!-- persistence -->
|
||||||
<tomcat-dbcp.version>9.0.0.M26</tomcat-dbcp.version>
|
<tomcat-dbcp.version>9.0.0.M26</tomcat-dbcp.version>
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Book> 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<Book> getBooks() {
|
||||||
|
return books;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBooks(Set<Book> books) {
|
||||||
|
this.books = books;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreRemove
|
||||||
|
private void removeBookAssociations() {
|
||||||
|
for (Book book : this.books) {
|
||||||
|
book.getAuthors().remove(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<Author> 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<Author> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Category> 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<Category> getCategories() {
|
||||||
|
return categories;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCategories(Set<Category> categories) {
|
||||||
|
this.categories = categories;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue