From 413c02c0784c799b2ac28e1cedb84131c146a69f Mon Sep 17 00:00:00 2001 From: Rudi Adianto Date: Sun, 12 Aug 2018 18:23:41 +0700 Subject: [PATCH] "An Intro to Hibernate Entity Lifecycle" "An Intro to Hibernate Entity Lifecycle" by Rudi --- .../lifecycle/DirtyDataInspector.java | 26 +++ .../hibernate/lifecycle/FootballPlayer.java | 41 +++++ .../hibernate/lifecycle/HibernateUtil.java | 100 +++++++++++ .../lifecycle/HibernateLifecycleUnitTest.java | 166 ++++++++++++++++++ .../resources/hibernate-lifecycle.properties | 9 + .../src/test/resources/lifecycle-init.sql | 26 +++ 6 files changed, 368 insertions(+) create mode 100644 hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/DirtyDataInspector.java create mode 100644 hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/FootballPlayer.java create mode 100644 hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/HibernateUtil.java create mode 100644 hibernate5/src/test/java/com/baeldung/hibernate/lifecycle/HibernateLifecycleUnitTest.java create mode 100644 hibernate5/src/test/resources/hibernate-lifecycle.properties create mode 100644 hibernate5/src/test/resources/lifecycle-init.sql diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/DirtyDataInspector.java b/hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/DirtyDataInspector.java new file mode 100644 index 0000000000..4e00be2b5c --- /dev/null +++ b/hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/DirtyDataInspector.java @@ -0,0 +1,26 @@ +package com.baeldung.hibernate.lifecycle; + +import org.hibernate.EmptyInterceptor; +import org.hibernate.type.Type; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +public class DirtyDataInspector extends EmptyInterceptor { + private static final ArrayList dirtyEntities = new ArrayList<>(); + + @Override + public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) { + dirtyEntities.add((FootballPlayer) entity); + return true; + } + + public static List getDirtyEntities() { + return dirtyEntities; + } + + public static void clearDirtyEntitites() { + dirtyEntities.clear(); + } +} \ No newline at end of file diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/FootballPlayer.java b/hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/FootballPlayer.java new file mode 100644 index 0000000000..139490fa2a --- /dev/null +++ b/hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/FootballPlayer.java @@ -0,0 +1,41 @@ +package com.baeldung.hibernate.lifecycle; + +import javax.persistence.*; + +@Entity +@Table(name = "Football_Player") +public class FootballPlayer { + @Id @GeneratedValue private long id; + + @Column private String name; + + @Column private String team; + + 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 String getTeam() { + return team; + } + + public void setTeam(String team) { + this.team = team; + } + + @Override public String toString() { + return "FootballPlayer{" + "id=" + id + ", name='" + name + '\'' + ", team='" + team + '\'' + '}'; + } +} \ No newline at end of file diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/HibernateUtil.java b/hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/HibernateUtil.java new file mode 100644 index 0000000000..ca46e3f2d4 --- /dev/null +++ b/hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/HibernateUtil.java @@ -0,0 +1,100 @@ +package com.baeldung.hibernate.lifecycle; + +import org.h2.tools.RunScript; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.Transaction; +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.service.ServiceRegistry; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.IOException; +import java.net.URL; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Collectors; + +public class HibernateUtil { + private static SessionFactory sessionFactory; + private static Connection connection; + + public static void init() throws Exception { + Connection dbConnection = null; + Class.forName("org.h2.Driver"); + + connection = connection = DriverManager.getConnection("jdbc:h2:mem:lifecycledb;DB_CLOSE_DELAY=-1;", "sa", ""); + File initFile = new File("src/test/resources/lifecycle-init.sql"); + try(FileReader reader = new FileReader(initFile);) { + RunScript.execute(connection, reader); + } + + ServiceRegistry serviceRegistry = configureServiceRegistry(); + sessionFactory = getSessionFactoryBuilder(serviceRegistry).applyInterceptor(new DirtyDataInspector()).build(); + } + + public static void tearDown() throws Exception { + sessionFactory.close(); + connection.close(); + } + + public static SessionFactory getSessionFactory() { + return sessionFactory; + } + + private static SessionFactoryBuilder getSessionFactoryBuilder(ServiceRegistry serviceRegistry) { + MetadataSources metadataSources = new MetadataSources(serviceRegistry); + metadataSources.addAnnotatedClass(FootballPlayer.class); + + Metadata metadata = metadataSources.buildMetadata(); + return metadata.getSessionFactoryBuilder(); + + } + + private static ServiceRegistry configureServiceRegistry() throws IOException { + Properties properties = getProperties(); + return new StandardServiceRegistryBuilder().applySettings(properties).build(); + } + + private static Properties getProperties() throws IOException { + Properties properties = new Properties(); + URL propertiesURL = Thread.currentThread().getContextClassLoader().getResource("hibernate-lifecycle.properties"); + try (FileInputStream inputStream = new FileInputStream(propertiesURL.getFile())) { + properties.load(inputStream); + } + return properties; + } + + public static List getManagedEntities(Session session) { + Map.Entry[] entries = ((SessionImplementor)session).getPersistenceContext().reentrantSafeEntityEntries(); + return Arrays.asList(entries).stream().map(e -> e.getValue()).collect(Collectors.toList()); + } + + public static Connection getDBConnection() throws Exception { + return connection; + } + + public static Transaction startTransaction(Session s) { + Transaction tx = s.getTransaction(); + tx.begin(); + return tx; + } + + public static int queryCount(String query) throws Exception { + try(ResultSet rs = connection.createStatement().executeQuery(query);) { + rs.next(); + return rs.getInt(1); + } + } +} diff --git a/hibernate5/src/test/java/com/baeldung/hibernate/lifecycle/HibernateLifecycleUnitTest.java b/hibernate5/src/test/java/com/baeldung/hibernate/lifecycle/HibernateLifecycleUnitTest.java new file mode 100644 index 0000000000..49aba05be8 --- /dev/null +++ b/hibernate5/src/test/java/com/baeldung/hibernate/lifecycle/HibernateLifecycleUnitTest.java @@ -0,0 +1,166 @@ +package com.baeldung.hibernate.lifecycle; + +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.Transaction; +import org.hibernate.engine.spi.Status; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.List; + +import static com.baeldung.hibernate.lifecycle.DirtyDataInspector.getDirtyEntities; +import static com.baeldung.hibernate.lifecycle.HibernateUtil.*; +import static org.assertj.core.api.Assertions.assertThat; + +public class HibernateLifecycleUnitTest { + + @BeforeClass + public static void setup() throws Exception { + HibernateUtil.init(); + + } + + @AfterClass + public static void tearDown() throws Exception { + HibernateUtil.tearDown(); + } + + @Before + public void beforeMethod() { + DirtyDataInspector.clearDirtyEntitites(); + } + + @Test + public void whenEntityLoaded_thenEntityManaged() throws Exception { + SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); + try(Session session = sessionFactory.openSession()) { + Transaction transaction = startTransaction(session); + + assertThat(getManagedEntities(session)).isEmpty(); + + List players = session.createQuery("from FootballPlayer").getResultList(); + assertThat(getManagedEntities(session)).size().isEqualTo(3); + + assertThat(getDirtyEntities()).isEmpty(); + + FootballPlayer gigiBuffon = players.stream() + .filter(p -> p.getId()==3) + .findFirst() + .get(); + + gigiBuffon.setName("Gianluigi Buffon"); + transaction.commit(); + + assertThat(getDirtyEntities()).size().isEqualTo(1); + assertThat(getDirtyEntities().get(0).getId()).isEqualTo(3); + assertThat(getDirtyEntities().get(0).getName()).isEqualTo("Gianluigi Buffon"); + } + } + + @Test + public void whenDetached_thenNotTracked() throws Exception { + SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); + try(Session session = sessionFactory.openSession()) { + Transaction transaction = startTransaction(session); + + FootballPlayer cr7 = session.get(FootballPlayer.class, Long.valueOf(1)); + assertThat(getManagedEntities(session)).size().isEqualTo(1); + assertThat(getManagedEntities(session).get(0).getId()).isEqualTo(cr7.getId()); + + session.evict(cr7); + assertThat(getManagedEntities(session)).size().isEqualTo(0); + + cr7.setName("CR7"); + transaction.commit(); + + assertThat(getDirtyEntities()).isEmpty(); + } + } + + @Test + public void whenReattached_thenTrackedAgain() throws Exception { + SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); + try(Session session = sessionFactory.openSession()) { + Transaction transaction = startTransaction(session); + + FootballPlayer messi = session.get(FootballPlayer.class, Long.valueOf(2)); + + session.evict(messi); + messi.setName("Leo Messi"); + transaction.commit(); + assertThat(getDirtyEntities()).isEmpty(); + + transaction = startTransaction(session); + session.update(messi); + transaction.commit(); + assertThat(getDirtyEntities()).size().isEqualTo(1); + } + } + + @Test + public void givenNewEntityWithID_whenReattached_thenManaged() throws Exception { + SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); + try (Session session = sessionFactory.openSession()) { + Transaction transaction = startTransaction(session); + + FootballPlayer gigi = new FootballPlayer(); + gigi.setId(3); + gigi.setName("Gigi the Legend"); + + session.update(gigi); + assertThat(getManagedEntities(session)).size().isEqualTo(1); + + transaction.commit(); + assertThat(getDirtyEntities()).size().isEqualTo(1); + } + } + + @Test + public void givenTransientEntity_whenSave_thenManaged() throws Exception { + SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); + try (Session session = sessionFactory.openSession()) { + Transaction transaction = startTransaction(session); + + FootballPlayer neymar = new FootballPlayer(); + neymar.setName("Neymar"); + + session.save(neymar); + assertThat(getManagedEntities(session)).size().isEqualTo(1); + assertThat(neymar.getId()).isNotNull(); + + int count = queryCount("select count(*) from Football_Player where name='Neymar'"); + assertThat(count).isEqualTo(0); + + transaction.commit(); + + count = queryCount("select count(*) from Football_Player where name='Neymar'"); + assertThat(count).isEqualTo(1); + + transaction = startTransaction(session); + session.delete(neymar); + transaction.commit(); + } + } + + @Test() + public void whenDelete_thenMarkDeleted() throws Exception { + SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); + try (Session session = sessionFactory.openSession()) { + Transaction transaction = startTransaction(session); + + FootballPlayer neymar = new FootballPlayer(); + neymar.setName("Neymar"); + + session.save(neymar); + transaction.commit(); + + transaction = startTransaction(session); + session.delete(neymar); + assertThat(getManagedEntities(session).get(0).getStatus()).isEqualTo(Status.DELETED); + transaction.commit(); + } + } +} diff --git a/hibernate5/src/test/resources/hibernate-lifecycle.properties b/hibernate5/src/test/resources/hibernate-lifecycle.properties new file mode 100644 index 0000000000..aada8c42a2 --- /dev/null +++ b/hibernate5/src/test/resources/hibernate-lifecycle.properties @@ -0,0 +1,9 @@ +hibernate.connection.driver_class=org.h2.Driver +hibernate.connection.url=jdbc:h2:mem:lifecycledb;DB_CLOSE_DELAY=-1; +hibernate.connection.username=sa +hibernate.connection.autocommit=true +jdbc.password= + +hibernate.dialect=org.hibernate.dialect.H2Dialect +hibernate.show_sql=true +hibernate.hbm2ddl.auto=validate \ No newline at end of file diff --git a/hibernate5/src/test/resources/lifecycle-init.sql b/hibernate5/src/test/resources/lifecycle-init.sql new file mode 100644 index 0000000000..22596f5179 --- /dev/null +++ b/hibernate5/src/test/resources/lifecycle-init.sql @@ -0,0 +1,26 @@ +create sequence hibernate_sequence start with 1 increment by 1; + +create table Football_Player ( + id bigint not null, + team varchar(255), + name varchar(255), + primary key (id) +); + +insert into + Football_Player + (team, name, id) + values + ('Juventus', 'Cristiano Ronaldo', next value for hibernate_sequence); + +insert into + Football_Player + (team, name, id) + values + ('Barcelona', 'Lionel Messi', next value for hibernate_sequence); + +insert into + Football_Player + (team, name, id) + values + ('PSG', 'Gigi Buffon', next value for hibernate_sequence); \ No newline at end of file