From d0a6a0ed922ad9148b0d1d4bf64681798f0082e8 Mon Sep 17 00:00:00 2001 From: Dragan Bozanovic Date: Mon, 5 Dec 2016 22:46:23 +0100 Subject: [PATCH] BAEL-446: Deleting objects with Hibernate. (#879) --- .../config/PersistenceJPAConfigL2Cache.java | 7 +- .../main/resources/persistence-h2.properties | 2 +- .../config/PersistenceJPAConfigDeletion.java | 17 ++ .../persistence/deletion/model/Bar.java | 60 +++++++ .../persistence/deletion/model/Baz.java | 48 ++++++ .../persistence/deletion/model/Foo.java | 63 +++++++ .../service/DeletionIntegrationTest.java | 159 ++++++++++++++++++ 7 files changed, 354 insertions(+), 2 deletions(-) create mode 100644 spring-jpa/src/test/java/org/baeldung/persistence/deletion/config/PersistenceJPAConfigDeletion.java create mode 100644 spring-jpa/src/test/java/org/baeldung/persistence/deletion/model/Bar.java create mode 100644 spring-jpa/src/test/java/org/baeldung/persistence/deletion/model/Baz.java create mode 100644 spring-jpa/src/test/java/org/baeldung/persistence/deletion/model/Foo.java create mode 100644 spring-jpa/src/test/java/org/baeldung/persistence/service/DeletionIntegrationTest.java diff --git a/spring-jpa/src/main/java/org/baeldung/config/PersistenceJPAConfigL2Cache.java b/spring-jpa/src/main/java/org/baeldung/config/PersistenceJPAConfigL2Cache.java index 3ca0dbf5e4..8768bac58c 100644 --- a/spring-jpa/src/main/java/org/baeldung/config/PersistenceJPAConfigL2Cache.java +++ b/spring-jpa/src/main/java/org/baeldung/config/PersistenceJPAConfigL2Cache.java @@ -40,7 +40,7 @@ public class PersistenceJPAConfigL2Cache { public LocalContainerEntityManagerFactoryBean entityManagerFactory() { final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(dataSource()); - em.setPackagesToScan(new String[] { "org.baeldung.persistence.model" }); + em.setPackagesToScan(getPackagesToScan()); final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); em.setJpaVendorAdapter(vendorAdapter); @@ -49,6 +49,10 @@ public class PersistenceJPAConfigL2Cache { return em; } + protected String[] getPackagesToScan() { + return new String[] { "org.baeldung.persistence.model" }; + } + @Bean public DataSource dataSource() { final DriverManagerDataSource dataSource = new DriverManagerDataSource(); @@ -78,6 +82,7 @@ public class PersistenceJPAConfigL2Cache { hibernateProperties.setProperty("hibernate.cache.use_second_level_cache", env.getProperty("hibernate.cache.use_second_level_cache")); hibernateProperties.setProperty("hibernate.cache.use_query_cache", env.getProperty("hibernate.cache.use_query_cache")); hibernateProperties.setProperty("hibernate.cache.region.factory_class", env.getProperty("hibernate.cache.region.factory_class")); + hibernateProperties.setProperty("hibernate.show_sql", env.getProperty("hibernate.show_sql")); return hibernateProperties; } diff --git a/spring-jpa/src/main/resources/persistence-h2.properties b/spring-jpa/src/main/resources/persistence-h2.properties index d195af5ec9..2c3e18b58d 100644 --- a/spring-jpa/src/main/resources/persistence-h2.properties +++ b/spring-jpa/src/main/resources/persistence-h2.properties @@ -6,7 +6,7 @@ jdbc.user=sa # hibernate.X hibernate.dialect=org.hibernate.dialect.H2Dialect -hibernate.show_sql=false +hibernate.show_sql=true hibernate.hbm2ddl.auto=create-drop hibernate.cache.use_second_level_cache=true hibernate.cache.use_query_cache=true diff --git a/spring-jpa/src/test/java/org/baeldung/persistence/deletion/config/PersistenceJPAConfigDeletion.java b/spring-jpa/src/test/java/org/baeldung/persistence/deletion/config/PersistenceJPAConfigDeletion.java new file mode 100644 index 0000000000..37388d1c51 --- /dev/null +++ b/spring-jpa/src/test/java/org/baeldung/persistence/deletion/config/PersistenceJPAConfigDeletion.java @@ -0,0 +1,17 @@ +package org.baeldung.persistence.deletion.config; + +import org.baeldung.config.PersistenceJPAConfigL2Cache; + +import java.util.Properties; + +public class PersistenceJPAConfigDeletion extends PersistenceJPAConfigL2Cache { + + public PersistenceJPAConfigDeletion() { + super(); + } + + @Override + protected String[] getPackagesToScan() { + return new String[] { "org.baeldung.persistence.deletion.model" }; + } +} \ No newline at end of file diff --git a/spring-jpa/src/test/java/org/baeldung/persistence/deletion/model/Bar.java b/spring-jpa/src/test/java/org/baeldung/persistence/deletion/model/Bar.java new file mode 100644 index 0000000000..26c4846fd2 --- /dev/null +++ b/spring-jpa/src/test/java/org/baeldung/persistence/deletion/model/Bar.java @@ -0,0 +1,60 @@ +package org.baeldung.persistence.deletion.model; + +import javax.persistence.*; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table(name = "BAR") +public class Bar { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private long id; + + @Column(nullable = false) + private String name; + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + List bazList = new ArrayList<>(); + + public Bar() { + super(); + } + + public Bar(final String name) { + super(); + this.name = name; + } + + public long getId() { + return id; + } + + public void setId(final long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public List getBazList() { + return bazList; + } + + public void setBazList(final List bazList) { + this.bazList = bazList; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Bar [name=").append(name).append("]"); + return builder.toString(); + } +} diff --git a/spring-jpa/src/test/java/org/baeldung/persistence/deletion/model/Baz.java b/spring-jpa/src/test/java/org/baeldung/persistence/deletion/model/Baz.java new file mode 100644 index 0000000000..2dad3e6654 --- /dev/null +++ b/spring-jpa/src/test/java/org/baeldung/persistence/deletion/model/Baz.java @@ -0,0 +1,48 @@ +package org.baeldung.persistence.deletion.model; + +import javax.persistence.*; +import java.util.List; + +@Entity +@Table(name = "BAZ") +public class Baz { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private long id; + + @Column(nullable = false) + private String name; + + public Baz() { + super(); + } + + public Baz(final String name) { + super(); + this.name = name; + } + + public long getId() { + return id; + } + + public void setId(final long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Bar [name=").append(name).append("]"); + return builder.toString(); + } +} diff --git a/spring-jpa/src/test/java/org/baeldung/persistence/deletion/model/Foo.java b/spring-jpa/src/test/java/org/baeldung/persistence/deletion/model/Foo.java new file mode 100644 index 0000000000..00fc34c166 --- /dev/null +++ b/spring-jpa/src/test/java/org/baeldung/persistence/deletion/model/Foo.java @@ -0,0 +1,63 @@ +package org.baeldung.persistence.deletion.model; + +import org.hibernate.annotations.Where; + +import javax.persistence.*; + +@Entity +@Table(name = "FOO") +@Where(clause = "DELETED = 0") +public class Foo { + + public Foo() { + super(); + } + + public Foo(final String name) { + super(); + this.name = name; + } + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "ID") + private long id; + + @Column(name = "NAME") + private String name; + + @Column(name = "DELETED") + private Integer deleted = 0; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @JoinColumn(name = "BAR_ID") + private Bar bar; + + public Bar getBar() { + return bar; + } + + public void setBar(final Bar bar) { + this.bar = bar; + } + + public long getId() { + return id; + } + + public void setId(final int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public void setDeleted() { + this.deleted = 1; + } +} diff --git a/spring-jpa/src/test/java/org/baeldung/persistence/service/DeletionIntegrationTest.java b/spring-jpa/src/test/java/org/baeldung/persistence/service/DeletionIntegrationTest.java new file mode 100644 index 0000000000..9e5c5fa07a --- /dev/null +++ b/spring-jpa/src/test/java/org/baeldung/persistence/service/DeletionIntegrationTest.java @@ -0,0 +1,159 @@ +package org.baeldung.persistence.service; + +import org.baeldung.persistence.deletion.config.PersistenceJPAConfigDeletion; +import org.baeldung.persistence.deletion.model.Bar; +import org.baeldung.persistence.deletion.model.Baz; +import org.baeldung.persistence.deletion.model.Foo; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.Transactional; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { PersistenceJPAConfigDeletion.class }, loader = AnnotationConfigContextLoader.class) +public class DeletionIntegrationTest { + + @PersistenceContext + private EntityManager entityManager; + @Autowired + private PlatformTransactionManager platformTransactionManager; + + @Before + public final void before() { + entityManager.getEntityManagerFactory().getCache().evictAll(); + } + + @Test + @Transactional + public final void givenEntityIsRemoved_thenItIsNotInDB() { + Foo foo = new Foo("foo"); + entityManager.persist(foo); + flushAndClear(); + + foo = entityManager.find(Foo.class, foo.getId()); + assertThat(foo, notNullValue()); + + entityManager.remove(foo); + flushAndClear(); + + assertThat(entityManager.find(Foo.class, foo.getId()), nullValue()); + } + + @Test + @Transactional + public final void givenEntityIsRemovedAndReferencedByAnotherEntity_thenItIsNotRemoved() { + Bar bar = new Bar("bar"); + Foo foo = new Foo("foo"); + foo.setBar(bar); + entityManager.persist(foo); + flushAndClear(); + + foo = entityManager.find(Foo.class, foo.getId()); + bar = entityManager.find(Bar.class, bar.getId()); + entityManager.remove(bar); + flushAndClear(); + + bar = entityManager.find(Bar.class, bar.getId()); + assertThat(bar, notNullValue()); + + foo = entityManager.find(Foo.class, foo.getId()); + foo.setBar(null); + entityManager.remove(bar); + flushAndClear(); + + assertThat(entityManager.find(Bar.class, bar.getId()), nullValue()); + } + + @Test + @Transactional + public final void givenEntityIsRemoved_thenRemovalIsCascaded() { + Bar bar = new Bar("bar"); + Foo foo = new Foo("foo"); + foo.setBar(bar); + entityManager.persist(foo); + flushAndClear(); + + foo = entityManager.find(Foo.class, foo.getId()); + entityManager.remove(foo); + flushAndClear(); + + assertThat(entityManager.find(Foo.class, foo.getId()), nullValue()); + assertThat(entityManager.find(Bar.class, bar.getId()), nullValue()); + } + + @Test + @Transactional + public final void givenEntityIsDisassociated_thenOrphanRemovalIsApplied() { + Bar bar = new Bar("bar"); + Baz baz = new Baz("baz"); + bar.getBazList().add(baz); + entityManager.persist(bar); + flushAndClear(); + + bar = entityManager.find(Bar.class, bar.getId()); + baz = bar.getBazList().get(0); + bar.getBazList().remove(baz); + flushAndClear(); + + assertThat(entityManager.find(Baz.class, baz.getId()), nullValue()); + } + + @Test + @Transactional + public final void givenEntityIsDeletedWithJpaBulkDeleteStatement_thenItIsNotInDB() { + Foo foo = new Foo("foo"); + entityManager.persist(foo); + flushAndClear(); + + entityManager.createQuery("delete from Foo where id = :id") + .setParameter("id", foo.getId()) + .executeUpdate(); + + assertThat(entityManager.find(Foo.class, foo.getId()), nullValue()); + } + + @Test + @Transactional + public final void givenEntityIsDeletedWithNativeQuery_thenItIsNotInDB() { + Foo foo = new Foo("foo"); + entityManager.persist(foo); + flushAndClear(); + + entityManager.createNativeQuery("delete from FOO where ID = :id") + .setParameter("id", foo.getId()) + .executeUpdate(); + + assertThat(entityManager.find(Foo.class, foo.getId()), nullValue()); + } + + @Test + @Transactional + public final void givenEntityIsSoftDeleted_thenItIsNotReturnedFromQueries() { + Foo foo = new Foo("foo"); + entityManager.persist(foo); + flushAndClear(); + + foo = entityManager.find(Foo.class, foo.getId()); + foo.setDeleted(); + flushAndClear(); + + assertThat(entityManager.find(Foo.class, foo.getId()), nullValue()); + } + + private void flushAndClear() { + entityManager.flush(); + entityManager.clear(); + } +}