From 413c02c0784c799b2ac28e1cedb84131c146a69f Mon Sep 17 00:00:00 2001 From: Rudi Adianto Date: Sun, 12 Aug 2018 18:23:41 +0700 Subject: [PATCH 01/11] "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 From b424ae7ab2fbac0a3b140fe846224dd81293314d Mon Sep 17 00:00:00 2001 From: Rudi Adianto Date: Mon, 13 Aug 2018 13:39:01 +0700 Subject: [PATCH 02/11] Revision from Predrag's feedback --- .../hibernate/lifecycle/FootballPlayer.java | 23 +++++++++++-------- .../lifecycle/HibernateLifecycleUnitTest.java | 1 + .../src/test/resources/lifecycle-init.sql | 8 +++---- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/FootballPlayer.java b/hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/FootballPlayer.java index 139490fa2a..2cda0255b6 100644 --- a/hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/FootballPlayer.java +++ b/hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/FootballPlayer.java @@ -5,11 +5,15 @@ import javax.persistence.*; @Entity @Table(name = "Football_Player") public class FootballPlayer { - @Id @GeneratedValue private long id; + @Id + @GeneratedValue + private long id; - @Column private String name; + @Column + private String name; - @Column private String team; + @Column + private String club; public long getId() { return id; @@ -27,15 +31,16 @@ public class FootballPlayer { this.name = name; } - public String getTeam() { - return team; + public String getClub() { + return club; } - public void setTeam(String team) { - this.team = team; + public void setClub(String club) { + this.club = club; } - @Override public String toString() { - return "FootballPlayer{" + "id=" + id + ", name='" + name + '\'' + ", team='" + team + '\'' + '}'; + @Override + public String toString() { + return "FootballPlayer{" + "id=" + id + ", name='" + name + '\'' + ", club='" + club + '\'' + '}'; } } \ No newline at end of file diff --git a/hibernate5/src/test/java/com/baeldung/hibernate/lifecycle/HibernateLifecycleUnitTest.java b/hibernate5/src/test/java/com/baeldung/hibernate/lifecycle/HibernateLifecycleUnitTest.java index 49aba05be8..dac17bf54b 100644 --- a/hibernate5/src/test/java/com/baeldung/hibernate/lifecycle/HibernateLifecycleUnitTest.java +++ b/hibernate5/src/test/java/com/baeldung/hibernate/lifecycle/HibernateLifecycleUnitTest.java @@ -97,6 +97,7 @@ public class HibernateLifecycleUnitTest { session.update(messi); transaction.commit(); assertThat(getDirtyEntities()).size().isEqualTo(1); + assertThat(getDirtyEntities().get(0).getName()).isEqualTo("Leo Messi"); } } diff --git a/hibernate5/src/test/resources/lifecycle-init.sql b/hibernate5/src/test/resources/lifecycle-init.sql index 22596f5179..02c72c2f88 100644 --- a/hibernate5/src/test/resources/lifecycle-init.sql +++ b/hibernate5/src/test/resources/lifecycle-init.sql @@ -2,25 +2,25 @@ create sequence hibernate_sequence start with 1 increment by 1; create table Football_Player ( id bigint not null, - team varchar(255), + club varchar(255), name varchar(255), primary key (id) ); insert into Football_Player - (team, name, id) + (club, name, id) values ('Juventus', 'Cristiano Ronaldo', next value for hibernate_sequence); insert into Football_Player - (team, name, id) + (club, name, id) values ('Barcelona', 'Lionel Messi', next value for hibernate_sequence); insert into Football_Player - (team, name, id) + (club, name, id) values ('PSG', 'Gigi Buffon', next value for hibernate_sequence); \ No newline at end of file From b8e56590f87d412d1724ef35a1d407e209766b0c Mon Sep 17 00:00:00 2001 From: Rudi Adianto Date: Tue, 14 Aug 2018 10:50:10 +0700 Subject: [PATCH 03/11] Another revision from Predrag's feedback --- .../hibernate/lifecycle/FootballPlayer.java | 13 +------- ...eUtil.java => HibernateLifecycleUtil.java} | 28 ++++++++-------- .../HibernateInterceptorUnitTest.java | 1 - .../lifecycle/HibernateLifecycleUnitTest.java | 33 +++++++++---------- .../resources/hibernate-lifecycle.properties | 2 +- .../src/test/resources/lifecycle-init.sql | 13 ++++---- 6 files changed, 36 insertions(+), 54 deletions(-) rename hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/{HibernateUtil.java => HibernateLifecycleUtil.java} (72%) diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/FootballPlayer.java b/hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/FootballPlayer.java index 2cda0255b6..49799a5292 100644 --- a/hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/FootballPlayer.java +++ b/hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/FootballPlayer.java @@ -12,9 +12,6 @@ public class FootballPlayer { @Column private String name; - @Column - private String club; - public long getId() { return id; } @@ -31,16 +28,8 @@ public class FootballPlayer { this.name = name; } - public String getClub() { - return club; - } - - public void setClub(String club) { - this.club = club; - } - @Override public String toString() { - return "FootballPlayer{" + "id=" + id + ", name='" + name + '\'' + ", club='" + club + '\'' + '}'; + return "FootballPlayer{" + "id=" + id + ", name='" + name + '\'' + '}'; } } \ 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/HibernateLifecycleUtil.java similarity index 72% rename from hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/HibernateUtil.java rename to hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/HibernateLifecycleUtil.java index ca46e3f2d4..e3ca8bd596 100644 --- a/hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/HibernateUtil.java +++ b/hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/HibernateLifecycleUtil.java @@ -12,10 +12,10 @@ 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.io.InputStream; +import java.io.InputStreamReader; import java.net.URL; import java.sql.Connection; import java.sql.DriverManager; @@ -26,18 +26,20 @@ import java.util.Map; import java.util.Properties; import java.util.stream.Collectors; -public class HibernateUtil { +public class HibernateLifecycleUtil { private static SessionFactory sessionFactory; private static Connection connection; public static void init() throws Exception { - Connection dbConnection = null; Class.forName("org.h2.Driver"); + Properties hbConfigProp = new Properties(); + try (InputStream hbPropStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("hibernate-lifecycle.properties"); + InputStream h2InitStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("lifecycle-init.sql"); + InputStreamReader h2InitReader = new InputStreamReader(h2InitStream)) { + hbConfigProp.load(hbPropStream); + connection = DriverManager.getConnection(hbConfigProp.getProperty("hibernate.connection.url"), hbConfigProp.getProperty("hibernate.connection.username"), hbConfigProp.getProperty("hibernate.connection.password")); - 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); + RunScript.execute(connection, h2InitReader); } ServiceRegistry serviceRegistry = configureServiceRegistry(); @@ -77,12 +79,8 @@ public class HibernateUtil { } 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; + Map.Entry[] entries = ((SessionImplementor) session).getPersistenceContext().reentrantSafeEntityEntries(); + return Arrays.stream(entries).map(e -> e.getValue()).collect(Collectors.toList()); } public static Transaction startTransaction(Session s) { @@ -92,7 +90,7 @@ public class HibernateUtil { } public static int queryCount(String query) throws Exception { - try(ResultSet rs = connection.createStatement().executeQuery(query);) { + try (ResultSet rs = connection.createStatement().executeQuery(query)) { rs.next(); return rs.getInt(1); } diff --git a/hibernate5/src/test/java/com/baeldung/hibernate/interceptors/HibernateInterceptorUnitTest.java b/hibernate5/src/test/java/com/baeldung/hibernate/interceptors/HibernateInterceptorUnitTest.java index 0049f3a6bd..e18e989905 100644 --- a/hibernate5/src/test/java/com/baeldung/hibernate/interceptors/HibernateInterceptorUnitTest.java +++ b/hibernate5/src/test/java/com/baeldung/hibernate/interceptors/HibernateInterceptorUnitTest.java @@ -58,5 +58,4 @@ public class HibernateInterceptorUnitTest { transaction.commit(); session.close(); } - } diff --git a/hibernate5/src/test/java/com/baeldung/hibernate/lifecycle/HibernateLifecycleUnitTest.java b/hibernate5/src/test/java/com/baeldung/hibernate/lifecycle/HibernateLifecycleUnitTest.java index dac17bf54b..e682b46481 100644 --- a/hibernate5/src/test/java/com/baeldung/hibernate/lifecycle/HibernateLifecycleUnitTest.java +++ b/hibernate5/src/test/java/com/baeldung/hibernate/lifecycle/HibernateLifecycleUnitTest.java @@ -12,20 +12,20 @@ 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 com.baeldung.hibernate.lifecycle.HibernateLifecycleUtil.*; import static org.assertj.core.api.Assertions.assertThat; public class HibernateLifecycleUnitTest { @BeforeClass public static void setup() throws Exception { - HibernateUtil.init(); + HibernateLifecycleUtil.init(); } @AfterClass public static void tearDown() throws Exception { - HibernateUtil.tearDown(); + HibernateLifecycleUtil.tearDown(); } @Before @@ -35,8 +35,8 @@ public class HibernateLifecycleUnitTest { @Test public void whenEntityLoaded_thenEntityManaged() throws Exception { - SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); - try(Session session = sessionFactory.openSession()) { + SessionFactory sessionFactory = HibernateLifecycleUtil.getSessionFactory(); + try (Session session = sessionFactory.openSession()) { Transaction transaction = startTransaction(session); assertThat(getManagedEntities(session)).isEmpty(); @@ -46,10 +46,7 @@ public class HibernateLifecycleUnitTest { assertThat(getDirtyEntities()).isEmpty(); - FootballPlayer gigiBuffon = players.stream() - .filter(p -> p.getId()==3) - .findFirst() - .get(); + FootballPlayer gigiBuffon = players.stream().filter(p -> p.getId() == 3).findFirst().get(); gigiBuffon.setName("Gianluigi Buffon"); transaction.commit(); @@ -62,11 +59,11 @@ public class HibernateLifecycleUnitTest { @Test public void whenDetached_thenNotTracked() throws Exception { - SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); - try(Session session = sessionFactory.openSession()) { + SessionFactory sessionFactory = HibernateLifecycleUtil.getSessionFactory(); + try (Session session = sessionFactory.openSession()) { Transaction transaction = startTransaction(session); - FootballPlayer cr7 = session.get(FootballPlayer.class, Long.valueOf(1)); + FootballPlayer cr7 = session.get(FootballPlayer.class, 1L); assertThat(getManagedEntities(session)).size().isEqualTo(1); assertThat(getManagedEntities(session).get(0).getId()).isEqualTo(cr7.getId()); @@ -82,11 +79,11 @@ public class HibernateLifecycleUnitTest { @Test public void whenReattached_thenTrackedAgain() throws Exception { - SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); - try(Session session = sessionFactory.openSession()) { + SessionFactory sessionFactory = HibernateLifecycleUtil.getSessionFactory(); + try (Session session = sessionFactory.openSession()) { Transaction transaction = startTransaction(session); - FootballPlayer messi = session.get(FootballPlayer.class, Long.valueOf(2)); + FootballPlayer messi = session.get(FootballPlayer.class, 2L); session.evict(messi); messi.setName("Leo Messi"); @@ -103,7 +100,7 @@ public class HibernateLifecycleUnitTest { @Test public void givenNewEntityWithID_whenReattached_thenManaged() throws Exception { - SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); + SessionFactory sessionFactory = HibernateLifecycleUtil.getSessionFactory(); try (Session session = sessionFactory.openSession()) { Transaction transaction = startTransaction(session); @@ -121,7 +118,7 @@ public class HibernateLifecycleUnitTest { @Test public void givenTransientEntity_whenSave_thenManaged() throws Exception { - SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); + SessionFactory sessionFactory = HibernateLifecycleUtil.getSessionFactory(); try (Session session = sessionFactory.openSession()) { Transaction transaction = startTransaction(session); @@ -148,7 +145,7 @@ public class HibernateLifecycleUnitTest { @Test() public void whenDelete_thenMarkDeleted() throws Exception { - SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); + SessionFactory sessionFactory = HibernateLifecycleUtil.getSessionFactory(); try (Session session = sessionFactory.openSession()) { Transaction transaction = startTransaction(session); diff --git a/hibernate5/src/test/resources/hibernate-lifecycle.properties b/hibernate5/src/test/resources/hibernate-lifecycle.properties index aada8c42a2..d043b624f2 100644 --- a/hibernate5/src/test/resources/hibernate-lifecycle.properties +++ b/hibernate5/src/test/resources/hibernate-lifecycle.properties @@ -1,8 +1,8 @@ hibernate.connection.driver_class=org.h2.Driver hibernate.connection.url=jdbc:h2:mem:lifecycledb;DB_CLOSE_DELAY=-1; hibernate.connection.username=sa +hibernate.connection.password= hibernate.connection.autocommit=true -jdbc.password= hibernate.dialect=org.hibernate.dialect.H2Dialect hibernate.show_sql=true diff --git a/hibernate5/src/test/resources/lifecycle-init.sql b/hibernate5/src/test/resources/lifecycle-init.sql index 02c72c2f88..c0c9a3f34d 100644 --- a/hibernate5/src/test/resources/lifecycle-init.sql +++ b/hibernate5/src/test/resources/lifecycle-init.sql @@ -2,25 +2,24 @@ create sequence hibernate_sequence start with 1 increment by 1; create table Football_Player ( id bigint not null, - club varchar(255), name varchar(255), primary key (id) ); insert into Football_Player - (club, name, id) + (name, id) values - ('Juventus', 'Cristiano Ronaldo', next value for hibernate_sequence); + ('Cristiano Ronaldo', next value for hibernate_sequence); insert into Football_Player - (club, name, id) + (name, id) values - ('Barcelona', 'Lionel Messi', next value for hibernate_sequence); + ('Lionel Messi', next value for hibernate_sequence); insert into Football_Player - (club, name, id) + (name, id) values - ('PSG', 'Gigi Buffon', next value for hibernate_sequence); \ No newline at end of file + ('Gigi Buffon', next value for hibernate_sequence); \ No newline at end of file From fc85b372998f3140fc86739f7f1cf5130beb528b Mon Sep 17 00:00:00 2001 From: Rudi Adianto Date: Tue, 14 Aug 2018 17:58:13 +0700 Subject: [PATCH 04/11] Another feedback. --- .../lifecycle/HibernateLifecycleUtil.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/HibernateLifecycleUtil.java b/hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/HibernateLifecycleUtil.java index e3ca8bd596..a06685fb9c 100644 --- a/hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/HibernateLifecycleUtil.java +++ b/hibernate5/src/main/java/com/baeldung/hibernate/lifecycle/HibernateLifecycleUtil.java @@ -31,14 +31,12 @@ public class HibernateLifecycleUtil { private static Connection connection; public static void init() throws Exception { - Class.forName("org.h2.Driver"); - Properties hbConfigProp = new Properties(); - try (InputStream hbPropStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("hibernate-lifecycle.properties"); - InputStream h2InitStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("lifecycle-init.sql"); - InputStreamReader h2InitReader = new InputStreamReader(h2InitStream)) { - hbConfigProp.load(hbPropStream); - connection = DriverManager.getConnection(hbConfigProp.getProperty("hibernate.connection.url"), hbConfigProp.getProperty("hibernate.connection.username"), hbConfigProp.getProperty("hibernate.connection.password")); + Properties hbConfigProp = getHibernateProperties(); + Class.forName(hbConfigProp.getProperty("hibernate.connection.driver_class")); + connection = DriverManager.getConnection(hbConfigProp.getProperty("hibernate.connection.url"), hbConfigProp.getProperty("hibernate.connection.username"), hbConfigProp.getProperty("hibernate.connection.password")); + try (InputStream h2InitStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("lifecycle-init.sql"); + InputStreamReader h2InitReader = new InputStreamReader(h2InitStream)) { RunScript.execute(connection, h2InitReader); } @@ -65,11 +63,11 @@ public class HibernateLifecycleUtil { } private static ServiceRegistry configureServiceRegistry() throws IOException { - Properties properties = getProperties(); + Properties properties = getHibernateProperties(); return new StandardServiceRegistryBuilder().applySettings(properties).build(); } - private static Properties getProperties() throws IOException { + private static Properties getHibernateProperties() throws IOException { Properties properties = new Properties(); URL propertiesURL = Thread.currentThread().getContextClassLoader().getResource("hibernate-lifecycle.properties"); try (FileInputStream inputStream = new FileInputStream(propertiesURL.getFile())) { From 2ead92d8516e064182c162089e3ebbcfb97f451e Mon Sep 17 00:00:00 2001 From: Rudi Adianto Date: Sun, 26 Aug 2018 13:37:06 +0700 Subject: [PATCH 05/11] BAEL-2081 --- jta/pom.xml | 60 ++++++++++++++++ .../baeldung/jtademo/JtaDemoApplication.java | 60 ++++++++++++++++ .../com/baeldung/jtademo/dto/TransferLog.java | 27 +++++++ .../jtademo/services/AuditService.java | 25 +++++++ .../services/BankAccountManualTxService.java | 27 +++++++ .../jtademo/services/BankAccountService.java | 27 +++++++ .../jtademo/services/TellerService.java | 32 +++++++++ .../baeldung/jtademo/services/TestHelper.java | 59 ++++++++++++++++ jta/src/main/resources/account.sql | 9 +++ jta/src/main/resources/application.properties | 0 jta/src/main/resources/audit.sql | 8 +++ .../com/baeldung/jtademo/JtaDemoUnitTest.java | 70 +++++++++++++++++++ pom.xml | 1 + 13 files changed, 405 insertions(+) create mode 100644 jta/pom.xml create mode 100644 jta/src/main/java/com/baeldung/jtademo/JtaDemoApplication.java create mode 100644 jta/src/main/java/com/baeldung/jtademo/dto/TransferLog.java create mode 100644 jta/src/main/java/com/baeldung/jtademo/services/AuditService.java create mode 100644 jta/src/main/java/com/baeldung/jtademo/services/BankAccountManualTxService.java create mode 100644 jta/src/main/java/com/baeldung/jtademo/services/BankAccountService.java create mode 100644 jta/src/main/java/com/baeldung/jtademo/services/TellerService.java create mode 100644 jta/src/main/java/com/baeldung/jtademo/services/TestHelper.java create mode 100644 jta/src/main/resources/account.sql create mode 100644 jta/src/main/resources/application.properties create mode 100644 jta/src/main/resources/audit.sql create mode 100644 jta/src/test/java/com/baeldung/jtademo/JtaDemoUnitTest.java diff --git a/jta/pom.xml b/jta/pom.xml new file mode 100644 index 0000000000..3eb0e294d5 --- /dev/null +++ b/jta/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + com.baeldung + jta-demo + 1.0-SNAPSHOT + jar + + JEE JTA demo + + + org.springframework.boot + spring-boot-starter-parent + 2.0.4.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-jta-bitronix + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.hsqldb + hsqldb + 2.4.1 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/jta/src/main/java/com/baeldung/jtademo/JtaDemoApplication.java b/jta/src/main/java/com/baeldung/jtademo/JtaDemoApplication.java new file mode 100644 index 0000000000..f7b1cbbb4d --- /dev/null +++ b/jta/src/main/java/com/baeldung/jtademo/JtaDemoApplication.java @@ -0,0 +1,60 @@ +package com.baeldung.jtademo; + +import com.baeldung.jtademo.services.TellerService; +import org.hsqldb.jdbc.JDBCDataSourceFactory; +import org.hsqldb.jdbc.pool.JDBCXADataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.jta.bitronix.BitronixXADataSourceWrapper; +import org.springframework.boot.jta.bitronix.PoolingDataSourceBean; +import org.springframework.context.annotation.Bean; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.init.ScriptUtils; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.util.Assert; + +import javax.sql.DataSource; +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.SQLException; + +@EnableAutoConfiguration +@EnableTransactionManagement +public class JtaDemoApplication { + + @Bean("dataSourceAccount") + public DataSource dataSource() throws Exception { + return createHsqlXADatasource("jdbc:hsqldb:mem:accountDb"); + } + + @Bean("dataSourceAudit") + public DataSource dataSourceAudit() throws Exception { + return createHsqlXADatasource("jdbc:hsqldb:mem:auditDb"); + } + + private DataSource createHsqlXADatasource(String connectionUrl) throws Exception { + JDBCXADataSource dataSource = new JDBCXADataSource(); + dataSource.setUrl(connectionUrl); + dataSource.setUser("sa"); + BitronixXADataSourceWrapper wrapper = new BitronixXADataSourceWrapper(); + return wrapper.wrapDataSource(dataSource); + } + + @Bean("jdbcTemplateAccount") + public JdbcTemplate jdbcTemplate(@Qualifier("dataSourceAccount") DataSource dataSource) { + return new JdbcTemplate(dataSource); + } + + @Bean("jdbcTemplateAudit") + public JdbcTemplate jdbcTemplateAudit(@Qualifier("dataSourceAudit") DataSource dataSource) { + return new JdbcTemplate(dataSource); + } +} diff --git a/jta/src/main/java/com/baeldung/jtademo/dto/TransferLog.java b/jta/src/main/java/com/baeldung/jtademo/dto/TransferLog.java new file mode 100644 index 0000000000..cc1474ce81 --- /dev/null +++ b/jta/src/main/java/com/baeldung/jtademo/dto/TransferLog.java @@ -0,0 +1,27 @@ +package com.baeldung.jtademo.dto; + +import java.math.BigDecimal; + +public class TransferLog { + private String fromAccountId; + private String toAccountId; + private BigDecimal amount; + + public TransferLog(String fromAccountId, String toAccountId, BigDecimal amount) { + this.fromAccountId = fromAccountId; + this.toAccountId = toAccountId; + this.amount = amount; + } + + public String getFromAccountId() { + return fromAccountId; + } + + public String getToAccountId() { + return toAccountId; + } + + public BigDecimal getAmount() { + return amount; + } +} diff --git a/jta/src/main/java/com/baeldung/jtademo/services/AuditService.java b/jta/src/main/java/com/baeldung/jtademo/services/AuditService.java new file mode 100644 index 0000000000..098a6f9e1f --- /dev/null +++ b/jta/src/main/java/com/baeldung/jtademo/services/AuditService.java @@ -0,0 +1,25 @@ +package com.baeldung.jtademo.services; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + +import javax.transaction.Transactional; +import java.math.BigDecimal; + +@Service +public class AuditService { + + final JdbcTemplate jdbcTemplate; + + @Autowired + public AuditService(@Qualifier("jdbcTemplateAudit") JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Transactional + public void log(String fromAccount, String toAccount, BigDecimal amount) { + jdbcTemplate.update("insert into AUDIT_LOG(FROM_ACCOUNT, TO_ACCOUNT, AMOUNT) values ?,?,?", fromAccount, toAccount, amount); + } +} diff --git a/jta/src/main/java/com/baeldung/jtademo/services/BankAccountManualTxService.java b/jta/src/main/java/com/baeldung/jtademo/services/BankAccountManualTxService.java new file mode 100644 index 0000000000..acbef33427 --- /dev/null +++ b/jta/src/main/java/com/baeldung/jtademo/services/BankAccountManualTxService.java @@ -0,0 +1,27 @@ +package com.baeldung.jtademo.services; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.transaction.UserTransaction; +import java.math.BigDecimal; + +@Component +public class BankAccountManualTxService { + @Resource + UserTransaction userTransaction; + + @Autowired + @Qualifier("jdbcTemplateAccount") + JdbcTemplate jdbcTemplate; + + public void transfer(String fromAccountId, String toAccountId, BigDecimal amount) throws Exception { + userTransaction.begin(); + jdbcTemplate.update("update ACCOUNT set BALANCE=BALANCE-? where ID=?", amount, fromAccountId); + jdbcTemplate.update("update ACCOUNT set BALANCE=BALANCE+? where ID=?", amount, toAccountId); + userTransaction.commit(); + } +} diff --git a/jta/src/main/java/com/baeldung/jtademo/services/BankAccountService.java b/jta/src/main/java/com/baeldung/jtademo/services/BankAccountService.java new file mode 100644 index 0000000000..bb59437add --- /dev/null +++ b/jta/src/main/java/com/baeldung/jtademo/services/BankAccountService.java @@ -0,0 +1,27 @@ +package com.baeldung.jtademo.services; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.stereotype.Service; + +import javax.transaction.Transactional; +import java.math.BigDecimal; + +@Service +public class BankAccountService { + + final JdbcTemplate jdbcTemplate; + + @Autowired + public BankAccountService(@Qualifier("jdbcTemplateAccount") JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Transactional + public void transfer(String fromAccountId, String toAccountId, BigDecimal amount) { + jdbcTemplate.update("update ACCOUNT set BALANCE=BALANCE-? where ID=?", amount, fromAccountId); + jdbcTemplate.update("update ACCOUNT set BALANCE=BALANCE+? where ID=?", amount, toAccountId); + } +} diff --git a/jta/src/main/java/com/baeldung/jtademo/services/TellerService.java b/jta/src/main/java/com/baeldung/jtademo/services/TellerService.java new file mode 100644 index 0000000000..8bef892ca2 --- /dev/null +++ b/jta/src/main/java/com/baeldung/jtademo/services/TellerService.java @@ -0,0 +1,32 @@ +package com.baeldung.jtademo.services; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.transaction.Transactional; +import java.math.BigDecimal; + +@Service +public class TellerService { + private final BankAccountService bankAccountService; + private final AuditService auditService; + + @Autowired + public TellerService(BankAccountService bankAccountService, AuditService auditService) { + this.bankAccountService = bankAccountService; + this.auditService = auditService; + } + + @Transactional + public void executeTransfer(String fromAccontId, String toAccountId, BigDecimal amount) { + bankAccountService.transfer(fromAccontId, toAccountId, amount); + auditService.log(fromAccontId, toAccountId, amount); + } + + @Transactional + public void executeTransferFail(String fromAccontId, String toAccountId, BigDecimal amount) { + bankAccountService.transfer(fromAccontId, toAccountId, amount); + auditService.log(fromAccontId, toAccountId, amount); + throw new RuntimeException("Something wrong, rollback!"); + } +} diff --git a/jta/src/main/java/com/baeldung/jtademo/services/TestHelper.java b/jta/src/main/java/com/baeldung/jtademo/services/TestHelper.java new file mode 100644 index 0000000000..21370cf034 --- /dev/null +++ b/jta/src/main/java/com/baeldung/jtademo/services/TestHelper.java @@ -0,0 +1,59 @@ +package com.baeldung.jtademo.services; + +import com.baeldung.jtademo.dto.TransferLog; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.jdbc.datasource.init.ScriptUtils; +import org.springframework.stereotype.Component; + +import javax.sql.DataSource; +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.SQLException; + +@Component +public class TestHelper { + final JdbcTemplate jdbcTemplateAccount; + + final JdbcTemplate jdbcTemplateAudit; + + @Autowired + public TestHelper(@Qualifier("jdbcTemplateAccount") JdbcTemplate jdbcTemplateAccount, @Qualifier("jdbcTemplateAudit") JdbcTemplate jdbcTemplateAudit) { + this.jdbcTemplateAccount = jdbcTemplateAccount; + this.jdbcTemplateAudit = jdbcTemplateAudit; + } + + public void runAccountDbInit() throws SQLException { + runScript("account.sql", jdbcTemplateAccount.getDataSource()); + } + + public void runAuditDbInit() throws SQLException { + runScript("audit.sql", jdbcTemplateAudit.getDataSource()); + } + + private void runScript(String scriptName, DataSource dataSouorce) throws SQLException { + DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); + Resource script = resourceLoader.getResource(scriptName); + try (Connection con = dataSouorce.getConnection()) { + ScriptUtils.executeSqlScript(con, script); + } + } + + public TransferLog lastTransferLog() { + return jdbcTemplateAudit.query("select FROM_ACCOUNT,TO_ACCOUNT,AMOUNT from AUDIT_LOG order by ID desc", (ResultSetExtractor) (rs) -> { + if(!rs.next()) return null; + return new TransferLog(rs.getString(1), rs.getString(2), BigDecimal.valueOf(rs.getDouble(3))); + }); + } + + public BigDecimal balanceOf(String accountId) { + return jdbcTemplateAccount.query("select BALANCE from ACCOUNT where ID=?", new Object[] { accountId }, (ResultSetExtractor) (rs) -> { + rs.next(); + return new BigDecimal(rs.getDouble(1)); + }); + } +} diff --git a/jta/src/main/resources/account.sql b/jta/src/main/resources/account.sql new file mode 100644 index 0000000000..af14f89b01 --- /dev/null +++ b/jta/src/main/resources/account.sql @@ -0,0 +1,9 @@ +DROP SCHEMA PUBLIC CASCADE; + +create table ACCOUNT ( +ID char(8) PRIMARY KEY, +BALANCE NUMERIC(28,10) +); + +insert into ACCOUNT(ID, BALANCE) values ('a0000001', 1000); +insert into ACCOUNT(ID, BALANCE) values ('a0000002', 2000); \ No newline at end of file diff --git a/jta/src/main/resources/application.properties b/jta/src/main/resources/application.properties new file mode 100644 index 0000000000..e69de29bb2 diff --git a/jta/src/main/resources/audit.sql b/jta/src/main/resources/audit.sql new file mode 100644 index 0000000000..aa5845f402 --- /dev/null +++ b/jta/src/main/resources/audit.sql @@ -0,0 +1,8 @@ +DROP SCHEMA PUBLIC CASCADE; + +create table AUDIT_LOG ( +ID INTEGER IDENTITY PRIMARY KEY, +FROM_ACCOUNT varchar(8), +TO_ACCOUNT varchar(8), +AMOUNT numeric(28,10) +); \ No newline at end of file diff --git a/jta/src/test/java/com/baeldung/jtademo/JtaDemoUnitTest.java b/jta/src/test/java/com/baeldung/jtademo/JtaDemoUnitTest.java new file mode 100644 index 0000000000..68ce5531a1 --- /dev/null +++ b/jta/src/test/java/com/baeldung/jtademo/JtaDemoUnitTest.java @@ -0,0 +1,70 @@ +package com.baeldung.jtademo; + +import com.baeldung.jtademo.dto.TransferLog; +import com.baeldung.jtademo.services.BankAccountManualTxService; +import com.baeldung.jtademo.services.TellerService; +import com.baeldung.jtademo.services.TestHelper; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.math.BigDecimal; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = JtaDemoApplication.class) +public class JtaDemoUnitTest { + @Autowired + TestHelper testHelper; + + @Autowired + TellerService tellerService; + + @Autowired + BankAccountManualTxService bankAccountManualTxService; + + @Before + public void beforeTest() throws Exception { + testHelper.runAuditDbInit(); + testHelper.runAccountDbInit(); + } + + @Test + public void whenNoException_thenAllCommitted() throws Exception { + tellerService.executeTransfer("a0000001", "a0000002", BigDecimal.valueOf(500)); + + BigDecimal result = testHelper.balanceOf("a0000001"); + assertThat(testHelper.balanceOf("a0000001")).isEqualByComparingTo(BigDecimal.valueOf(500)); + assertThat(testHelper.balanceOf("a0000002")).isEqualByComparingTo(BigDecimal.valueOf(2500)); + + TransferLog lastTransferLog = testHelper.lastTransferLog(); + assertThat(lastTransferLog).isNotNull(); + assertThat(lastTransferLog.getFromAccountId()).isEqualTo("a0000001"); + assertThat(lastTransferLog.getToAccountId()).isEqualTo("a0000002"); + assertThat(lastTransferLog.getAmount()).isEqualByComparingTo(BigDecimal.valueOf(500)); + } + + @Test + public void whenException_thenAllRolledBack() throws Exception { + assertThatThrownBy(() -> { + tellerService.executeTransferFail("a0000002", "a0000001", BigDecimal.valueOf(100)); + }).hasMessage("Something wrong, rollback!"); + + assertThat(testHelper.balanceOf("a0000001")).isEqualByComparingTo(BigDecimal.valueOf(1000)); + assertThat(testHelper.balanceOf("a0000002")).isEqualByComparingTo(BigDecimal.valueOf(2000)); + + assertThat(testHelper.lastTransferLog()).isNull(); + } + + @Test + public void givenBMT_whenNoException_thenAllCommitted() throws Exception { + bankAccountManualTxService.transfer("a0000001", "a0000002", BigDecimal.valueOf(100)); + + assertThat(testHelper.balanceOf("a0000001")).isEqualByComparingTo(BigDecimal.valueOf(900)); + assertThat(testHelper.balanceOf("a0000002")).isEqualByComparingTo(BigDecimal.valueOf(2100)); + } +} diff --git a/pom.xml b/pom.xml index 7ff1aa7d47..b95a874537 100644 --- a/pom.xml +++ b/pom.xml @@ -593,6 +593,7 @@ spring-reactive-kotlin jnosql spring-boot-angular-ecommerce + jta From 4fb5c5adc4a74aef1967f7ba246cdc227ccb5ae4 Mon Sep 17 00:00:00 2001 From: Rudi Adianto Date: Sun, 26 Aug 2018 14:13:24 +0700 Subject: [PATCH 06/11] ooptimize imports --- .../baeldung/jtademo/JtaDemoApplication.java | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/jta/src/main/java/com/baeldung/jtademo/JtaDemoApplication.java b/jta/src/main/java/com/baeldung/jtademo/JtaDemoApplication.java index f7b1cbbb4d..cb54562c87 100644 --- a/jta/src/main/java/com/baeldung/jtademo/JtaDemoApplication.java +++ b/jta/src/main/java/com/baeldung/jtademo/JtaDemoApplication.java @@ -1,32 +1,18 @@ package com.baeldung.jtademo; -import com.baeldung.jtademo.services.TellerService; -import org.hsqldb.jdbc.JDBCDataSourceFactory; import org.hsqldb.jdbc.pool.JDBCXADataSource; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.CommandLineRunner; -import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.jta.bitronix.BitronixXADataSourceWrapper; -import org.springframework.boot.jta.bitronix.PoolingDataSourceBean; import org.springframework.context.annotation.Bean; -import org.springframework.core.io.DefaultResourceLoader; -import org.springframework.core.io.Resource; +import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.ResultSetExtractor; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.init.ScriptUtils; import org.springframework.transaction.annotation.EnableTransactionManagement; -import org.springframework.util.Assert; import javax.sql.DataSource; -import java.math.BigDecimal; -import java.sql.Connection; -import java.sql.SQLException; -@EnableAutoConfiguration +@SpringBootApplication() @EnableTransactionManagement public class JtaDemoApplication { From 21dc3d06f0eddf84dc95a96ed778217678da176c Mon Sep 17 00:00:00 2001 From: Rudi Adianto Date: Sun, 26 Aug 2018 14:16:05 +0700 Subject: [PATCH 07/11] optimize imports --- jta/src/main/java/com/baeldung/jtademo/JtaDemoApplication.java | 2 -- .../java/com/baeldung/jtademo/services/BankAccountService.java | 1 - 2 files changed, 3 deletions(-) diff --git a/jta/src/main/java/com/baeldung/jtademo/JtaDemoApplication.java b/jta/src/main/java/com/baeldung/jtademo/JtaDemoApplication.java index cb54562c87..55258e3f23 100644 --- a/jta/src/main/java/com/baeldung/jtademo/JtaDemoApplication.java +++ b/jta/src/main/java/com/baeldung/jtademo/JtaDemoApplication.java @@ -2,11 +2,9 @@ package com.baeldung.jtademo; import org.hsqldb.jdbc.pool.JDBCXADataSource; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.jta.bitronix.BitronixXADataSourceWrapper; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.transaction.annotation.EnableTransactionManagement; diff --git a/jta/src/main/java/com/baeldung/jtademo/services/BankAccountService.java b/jta/src/main/java/com/baeldung/jtademo/services/BankAccountService.java index bb59437add..cd4aa253c5 100644 --- a/jta/src/main/java/com/baeldung/jtademo/services/BankAccountService.java +++ b/jta/src/main/java/com/baeldung/jtademo/services/BankAccountService.java @@ -3,7 +3,6 @@ package com.baeldung.jtademo.services; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.stereotype.Service; import javax.transaction.Transactional; From 33b65622a56155b3b9dcc2078d1d6d4408f53610 Mon Sep 17 00:00:00 2001 From: Rudi Adianto Date: Sun, 26 Aug 2018 14:33:23 +0700 Subject: [PATCH 08/11] pom.xml - removed spring boot maven plugin - add test exclusion rule --- jta/pom.xml | 44 +++++++++++++++---- .../baeldung/jtademo/JtaDemoApplication.java | 7 ++- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/jta/pom.xml b/jta/pom.xml index 3eb0e294d5..9a58c57d22 100644 --- a/jta/pom.xml +++ b/jta/pom.xml @@ -49,12 +49,40 @@ - - - - org.springframework.boot - spring-boot-maven-plugin - - - + + + autoconfiguration + + + + org.apache.maven.plugins + maven-surefire-plugin + + + integration-test + + test + + + + **/*LiveTest.java + **/*IntegrationTest.java + **/*IntTest.java + + + **/AutoconfigurationTest.java + + + + + + + json + + + + + + + diff --git a/jta/src/main/java/com/baeldung/jtademo/JtaDemoApplication.java b/jta/src/main/java/com/baeldung/jtademo/JtaDemoApplication.java index 55258e3f23..037958c38a 100644 --- a/jta/src/main/java/com/baeldung/jtademo/JtaDemoApplication.java +++ b/jta/src/main/java/com/baeldung/jtademo/JtaDemoApplication.java @@ -2,16 +2,21 @@ package com.baeldung.jtademo; import org.hsqldb.jdbc.pool.JDBCXADataSource; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.jta.bitronix.BitronixXADataSourceWrapper; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.sql.DataSource; -@SpringBootApplication() +@EnableAutoConfiguration @EnableTransactionManagement +@Configuration +@ComponentScan public class JtaDemoApplication { @Bean("dataSourceAccount") From 9325f26b245c572675b2e57129570c0ebfe60124 Mon Sep 17 00:00:00 2001 From: Rudi Adianto Date: Wed, 29 Aug 2018 14:45:57 +0700 Subject: [PATCH 09/11] revision - @Transaction and programmatic using the same example case. - XML format --- .../java/com/baeldung/jta/AuditService.java | 5 + jta/pom.xml | 136 +++++++++--------- .../jtademo/services/AuditService.java | 10 +- .../services/BankAccountManualTxService.java | 27 ---- .../jtademo/services/BankAccountService.java | 9 +- .../jtademo/services/TellerService.java | 21 ++- .../baeldung/jtademo/services/TestHelper.java | 12 -- .../com/baeldung/jtademo/JtaDemoUnitTest.java | 57 +++++--- 8 files changed, 144 insertions(+), 133 deletions(-) create mode 100644 jee-7/src/main/java/com/baeldung/jta/AuditService.java delete mode 100644 jta/src/main/java/com/baeldung/jtademo/services/BankAccountManualTxService.java diff --git a/jee-7/src/main/java/com/baeldung/jta/AuditService.java b/jee-7/src/main/java/com/baeldung/jta/AuditService.java new file mode 100644 index 0000000000..f9253b9d74 --- /dev/null +++ b/jee-7/src/main/java/com/baeldung/jta/AuditService.java @@ -0,0 +1,5 @@ +package com.baeldung.jta; + +public class AuditService { + +} diff --git a/jta/pom.xml b/jta/pom.xml index 9a58c57d22..89bdccf25e 100644 --- a/jta/pom.xml +++ b/jta/pom.xml @@ -1,46 +1,46 @@ - 4.0.0 - com.baeldung - jta-demo - 1.0-SNAPSHOT - jar + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + com.baeldung + jta-demo + 1.0-SNAPSHOT + jar - JEE JTA demo + JEE JTA demo - - org.springframework.boot - spring-boot-starter-parent - 2.0.4.RELEASE - - + + org.springframework.boot + spring-boot-starter-parent + 2.0.4.RELEASE + + - - UTF-8 - UTF-8 - 1.8 - + + UTF-8 + UTF-8 + 1.8 + - - - org.springframework.boot - spring-boot-starter-jta-bitronix - + + + org.springframework.boot + spring-boot-starter-jta-bitronix + org.springframework.boot spring-boot-starter-jdbc - - org.springframework.boot - spring-boot-starter - + + org.springframework.boot + spring-boot-starter + - - org.springframework.boot - spring-boot-starter-test - test - + + org.springframework.boot + spring-boot-starter-test + test + org.hsqldb @@ -49,40 +49,40 @@ - - - autoconfiguration - - - - org.apache.maven.plugins - maven-surefire-plugin - - - integration-test - - test - - - - **/*LiveTest.java - **/*IntegrationTest.java - **/*IntTest.java - - - **/AutoconfigurationTest.java - - - - - - - json - - - - - - - + + + autoconfiguration + + + + org.apache.maven.plugins + maven-surefire-plugin + + + integration-test + + test + + + + **/*LiveTest.java + **/*IntegrationTest.java + **/*IntTest.java + + + **/AutoconfigurationTest.java + + + + + + + json + + + + + + + diff --git a/jta/src/main/java/com/baeldung/jtademo/services/AuditService.java b/jta/src/main/java/com/baeldung/jtademo/services/AuditService.java index 098a6f9e1f..4eb8071757 100644 --- a/jta/src/main/java/com/baeldung/jtademo/services/AuditService.java +++ b/jta/src/main/java/com/baeldung/jtademo/services/AuditService.java @@ -1,8 +1,10 @@ package com.baeldung.jtademo.services; +import com.baeldung.jtademo.dto.TransferLog; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.stereotype.Service; import javax.transaction.Transactional; @@ -18,8 +20,14 @@ public class AuditService { this.jdbcTemplate = jdbcTemplate; } - @Transactional public void log(String fromAccount, String toAccount, BigDecimal amount) { jdbcTemplate.update("insert into AUDIT_LOG(FROM_ACCOUNT, TO_ACCOUNT, AMOUNT) values ?,?,?", fromAccount, toAccount, amount); } + + public TransferLog lastTransferLog() { + return jdbcTemplate.query("select FROM_ACCOUNT,TO_ACCOUNT,AMOUNT from AUDIT_LOG order by ID desc", (ResultSetExtractor) (rs) -> { + if(!rs.next()) return null; + return new TransferLog(rs.getString(1), rs.getString(2), BigDecimal.valueOf(rs.getDouble(3))); + }); + } } diff --git a/jta/src/main/java/com/baeldung/jtademo/services/BankAccountManualTxService.java b/jta/src/main/java/com/baeldung/jtademo/services/BankAccountManualTxService.java deleted file mode 100644 index acbef33427..0000000000 --- a/jta/src/main/java/com/baeldung/jtademo/services/BankAccountManualTxService.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.baeldung.jtademo.services; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.stereotype.Component; - -import javax.annotation.Resource; -import javax.transaction.UserTransaction; -import java.math.BigDecimal; - -@Component -public class BankAccountManualTxService { - @Resource - UserTransaction userTransaction; - - @Autowired - @Qualifier("jdbcTemplateAccount") - JdbcTemplate jdbcTemplate; - - public void transfer(String fromAccountId, String toAccountId, BigDecimal amount) throws Exception { - userTransaction.begin(); - jdbcTemplate.update("update ACCOUNT set BALANCE=BALANCE-? where ID=?", amount, fromAccountId); - jdbcTemplate.update("update ACCOUNT set BALANCE=BALANCE+? where ID=?", amount, toAccountId); - userTransaction.commit(); - } -} diff --git a/jta/src/main/java/com/baeldung/jtademo/services/BankAccountService.java b/jta/src/main/java/com/baeldung/jtademo/services/BankAccountService.java index cd4aa253c5..a50628b109 100644 --- a/jta/src/main/java/com/baeldung/jtademo/services/BankAccountService.java +++ b/jta/src/main/java/com/baeldung/jtademo/services/BankAccountService.java @@ -3,6 +3,7 @@ package com.baeldung.jtademo.services; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.stereotype.Service; import javax.transaction.Transactional; @@ -18,9 +19,15 @@ public class BankAccountService { this.jdbcTemplate = jdbcTemplate; } - @Transactional public void transfer(String fromAccountId, String toAccountId, BigDecimal amount) { jdbcTemplate.update("update ACCOUNT set BALANCE=BALANCE-? where ID=?", amount, fromAccountId); jdbcTemplate.update("update ACCOUNT set BALANCE=BALANCE+? where ID=?", amount, toAccountId); } + + public BigDecimal balanceOf(String accountId) { + return jdbcTemplate.query("select BALANCE from ACCOUNT where ID=?", new Object[] { accountId }, (ResultSetExtractor) (rs) -> { + rs.next(); + return new BigDecimal(rs.getDouble(1)); + }); + } } diff --git a/jta/src/main/java/com/baeldung/jtademo/services/TellerService.java b/jta/src/main/java/com/baeldung/jtademo/services/TellerService.java index 8bef892ca2..2bec79b47e 100644 --- a/jta/src/main/java/com/baeldung/jtademo/services/TellerService.java +++ b/jta/src/main/java/com/baeldung/jtademo/services/TellerService.java @@ -4,29 +4,42 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.transaction.Transactional; +import javax.transaction.UserTransaction; import java.math.BigDecimal; @Service public class TellerService { private final BankAccountService bankAccountService; private final AuditService auditService; + private final UserTransaction userTransaction; @Autowired - public TellerService(BankAccountService bankAccountService, AuditService auditService) { + public TellerService(BankAccountService bankAccountService, AuditService auditService, UserTransaction userTransaction) { this.bankAccountService = bankAccountService; this.auditService = auditService; + this.userTransaction = userTransaction; } @Transactional public void executeTransfer(String fromAccontId, String toAccountId, BigDecimal amount) { bankAccountService.transfer(fromAccontId, toAccountId, amount); auditService.log(fromAccontId, toAccountId, amount); + BigDecimal balance = bankAccountService.balanceOf(fromAccontId); + if(balance.compareTo(BigDecimal.ZERO) <= 0) { + throw new RuntimeException("Insufficient fund."); + } } - @Transactional - public void executeTransferFail(String fromAccontId, String toAccountId, BigDecimal amount) { + public void executeTransferProgrammaticTx(String fromAccontId, String toAccountId, BigDecimal amount) throws Exception { + userTransaction.begin(); bankAccountService.transfer(fromAccontId, toAccountId, amount); auditService.log(fromAccontId, toAccountId, amount); - throw new RuntimeException("Something wrong, rollback!"); + BigDecimal balance = bankAccountService.balanceOf(fromAccontId); + if(balance.compareTo(BigDecimal.ZERO) <= 0) { + userTransaction.rollback(); + throw new RuntimeException("Insufficient fund."); + } else { + userTransaction.commit(); + } } } diff --git a/jta/src/main/java/com/baeldung/jtademo/services/TestHelper.java b/jta/src/main/java/com/baeldung/jtademo/services/TestHelper.java index 21370cf034..7b818b3771 100644 --- a/jta/src/main/java/com/baeldung/jtademo/services/TestHelper.java +++ b/jta/src/main/java/com/baeldung/jtademo/services/TestHelper.java @@ -43,17 +43,5 @@ public class TestHelper { } } - public TransferLog lastTransferLog() { - return jdbcTemplateAudit.query("select FROM_ACCOUNT,TO_ACCOUNT,AMOUNT from AUDIT_LOG order by ID desc", (ResultSetExtractor) (rs) -> { - if(!rs.next()) return null; - return new TransferLog(rs.getString(1), rs.getString(2), BigDecimal.valueOf(rs.getDouble(3))); - }); - } - public BigDecimal balanceOf(String accountId) { - return jdbcTemplateAccount.query("select BALANCE from ACCOUNT where ID=?", new Object[] { accountId }, (ResultSetExtractor) (rs) -> { - rs.next(); - return new BigDecimal(rs.getDouble(1)); - }); - } } diff --git a/jta/src/test/java/com/baeldung/jtademo/JtaDemoUnitTest.java b/jta/src/test/java/com/baeldung/jtademo/JtaDemoUnitTest.java index 68ce5531a1..2f72098c41 100644 --- a/jta/src/test/java/com/baeldung/jtademo/JtaDemoUnitTest.java +++ b/jta/src/test/java/com/baeldung/jtademo/JtaDemoUnitTest.java @@ -1,9 +1,7 @@ package com.baeldung.jtademo; import com.baeldung.jtademo.dto.TransferLog; -import com.baeldung.jtademo.services.BankAccountManualTxService; -import com.baeldung.jtademo.services.TellerService; -import com.baeldung.jtademo.services.TestHelper; +import com.baeldung.jtademo.services.*; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -25,7 +23,10 @@ public class JtaDemoUnitTest { TellerService tellerService; @Autowired - BankAccountManualTxService bankAccountManualTxService; + BankAccountService accountService; + + @Autowired + AuditService auditService; @Before public void beforeTest() throws Exception { @@ -34,14 +35,13 @@ public class JtaDemoUnitTest { } @Test - public void whenNoException_thenAllCommitted() throws Exception { + public void givenAnnotationTx_whenNoException_thenAllCommitted() throws Exception { tellerService.executeTransfer("a0000001", "a0000002", BigDecimal.valueOf(500)); - BigDecimal result = testHelper.balanceOf("a0000001"); - assertThat(testHelper.balanceOf("a0000001")).isEqualByComparingTo(BigDecimal.valueOf(500)); - assertThat(testHelper.balanceOf("a0000002")).isEqualByComparingTo(BigDecimal.valueOf(2500)); + assertThat(accountService.balanceOf("a0000001")).isEqualByComparingTo(BigDecimal.valueOf(500)); + assertThat(accountService.balanceOf("a0000002")).isEqualByComparingTo(BigDecimal.valueOf(2500)); - TransferLog lastTransferLog = testHelper.lastTransferLog(); + TransferLog lastTransferLog = auditService.lastTransferLog(); assertThat(lastTransferLog).isNotNull(); assertThat(lastTransferLog.getFromAccountId()).isEqualTo("a0000001"); assertThat(lastTransferLog.getToAccountId()).isEqualTo("a0000002"); @@ -49,22 +49,39 @@ public class JtaDemoUnitTest { } @Test - public void whenException_thenAllRolledBack() throws Exception { + public void givenAnnotationTx_whenException_thenAllRolledBack() throws Exception { assertThatThrownBy(() -> { - tellerService.executeTransferFail("a0000002", "a0000001", BigDecimal.valueOf(100)); - }).hasMessage("Something wrong, rollback!"); + tellerService.executeTransfer("a0000002", "a0000001", BigDecimal.valueOf(100000)); + }).hasMessage("Insufficient fund."); - assertThat(testHelper.balanceOf("a0000001")).isEqualByComparingTo(BigDecimal.valueOf(1000)); - assertThat(testHelper.balanceOf("a0000002")).isEqualByComparingTo(BigDecimal.valueOf(2000)); - - assertThat(testHelper.lastTransferLog()).isNull(); + assertThat(accountService.balanceOf("a0000001")).isEqualByComparingTo(BigDecimal.valueOf(1000)); + assertThat(accountService.balanceOf("a0000002")).isEqualByComparingTo(BigDecimal.valueOf(2000)); + assertThat(auditService.lastTransferLog()).isNull(); } @Test - public void givenBMT_whenNoException_thenAllCommitted() throws Exception { - bankAccountManualTxService.transfer("a0000001", "a0000002", BigDecimal.valueOf(100)); + public void givenProgrammaticTx_whenCommit_thenAllCommitted() throws Exception { + tellerService.executeTransferProgrammaticTx("a0000001", "a0000002", BigDecimal.valueOf(500)); - assertThat(testHelper.balanceOf("a0000001")).isEqualByComparingTo(BigDecimal.valueOf(900)); - assertThat(testHelper.balanceOf("a0000002")).isEqualByComparingTo(BigDecimal.valueOf(2100)); + BigDecimal result = accountService.balanceOf("a0000001"); + assertThat(accountService.balanceOf("a0000001")).isEqualByComparingTo(BigDecimal.valueOf(500)); + assertThat(accountService.balanceOf("a0000002")).isEqualByComparingTo(BigDecimal.valueOf(2500)); + + TransferLog lastTransferLog = auditService.lastTransferLog(); + assertThat(lastTransferLog).isNotNull(); + assertThat(lastTransferLog.getFromAccountId()).isEqualTo("a0000001"); + assertThat(lastTransferLog.getToAccountId()).isEqualTo("a0000002"); + assertThat(lastTransferLog.getAmount()).isEqualByComparingTo(BigDecimal.valueOf(500)); + } + + @Test + public void givenProgrammaticTx_whenRollback_thenAllRolledBack() throws Exception { + assertThatThrownBy(() -> { + tellerService.executeTransferProgrammaticTx("a0000002", "a0000001", BigDecimal.valueOf(100000)); + }).hasMessage("Insufficient fund."); + + assertThat(accountService.balanceOf("a0000001")).isEqualByComparingTo(BigDecimal.valueOf(1000)); + assertThat(accountService.balanceOf("a0000002")).isEqualByComparingTo(BigDecimal.valueOf(2000)); + assertThat(auditService.lastTransferLog()).isNull(); } } From 43b920cc8ac66f5504210eddf7e4b6a90a8d3f9f Mon Sep 17 00:00:00 2001 From: Rudi Adianto Date: Wed, 29 Aug 2018 14:51:21 +0700 Subject: [PATCH 10/11] reformats & optimize imports --- .../java/com/baeldung/jtademo/JtaDemoApplication.java | 1 - .../com/baeldung/jtademo/services/AuditService.java | 4 ++-- .../baeldung/jtademo/services/BankAccountService.java | 1 - .../com/baeldung/jtademo/services/TellerService.java | 4 ++-- .../java/com/baeldung/jtademo/services/TestHelper.java | 4 ---- .../java/com/baeldung/jtademo/JtaDemoUnitTest.java | 10 +++++++--- 6 files changed, 11 insertions(+), 13 deletions(-) diff --git a/jta/src/main/java/com/baeldung/jtademo/JtaDemoApplication.java b/jta/src/main/java/com/baeldung/jtademo/JtaDemoApplication.java index 037958c38a..4d8779efe5 100644 --- a/jta/src/main/java/com/baeldung/jtademo/JtaDemoApplication.java +++ b/jta/src/main/java/com/baeldung/jtademo/JtaDemoApplication.java @@ -3,7 +3,6 @@ package com.baeldung.jtademo; import org.hsqldb.jdbc.pool.JDBCXADataSource; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.jta.bitronix.BitronixXADataSourceWrapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; diff --git a/jta/src/main/java/com/baeldung/jtademo/services/AuditService.java b/jta/src/main/java/com/baeldung/jtademo/services/AuditService.java index 4eb8071757..f6810e15c8 100644 --- a/jta/src/main/java/com/baeldung/jtademo/services/AuditService.java +++ b/jta/src/main/java/com/baeldung/jtademo/services/AuditService.java @@ -7,7 +7,6 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.stereotype.Service; -import javax.transaction.Transactional; import java.math.BigDecimal; @Service @@ -26,7 +25,8 @@ public class AuditService { public TransferLog lastTransferLog() { return jdbcTemplate.query("select FROM_ACCOUNT,TO_ACCOUNT,AMOUNT from AUDIT_LOG order by ID desc", (ResultSetExtractor) (rs) -> { - if(!rs.next()) return null; + if (!rs.next()) + return null; return new TransferLog(rs.getString(1), rs.getString(2), BigDecimal.valueOf(rs.getDouble(3))); }); } diff --git a/jta/src/main/java/com/baeldung/jtademo/services/BankAccountService.java b/jta/src/main/java/com/baeldung/jtademo/services/BankAccountService.java index a50628b109..0c881edbaa 100644 --- a/jta/src/main/java/com/baeldung/jtademo/services/BankAccountService.java +++ b/jta/src/main/java/com/baeldung/jtademo/services/BankAccountService.java @@ -6,7 +6,6 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.stereotype.Service; -import javax.transaction.Transactional; import java.math.BigDecimal; @Service diff --git a/jta/src/main/java/com/baeldung/jtademo/services/TellerService.java b/jta/src/main/java/com/baeldung/jtademo/services/TellerService.java index 2bec79b47e..d3bd80a2ee 100644 --- a/jta/src/main/java/com/baeldung/jtademo/services/TellerService.java +++ b/jta/src/main/java/com/baeldung/jtademo/services/TellerService.java @@ -25,7 +25,7 @@ public class TellerService { bankAccountService.transfer(fromAccontId, toAccountId, amount); auditService.log(fromAccontId, toAccountId, amount); BigDecimal balance = bankAccountService.balanceOf(fromAccontId); - if(balance.compareTo(BigDecimal.ZERO) <= 0) { + if (balance.compareTo(BigDecimal.ZERO) <= 0) { throw new RuntimeException("Insufficient fund."); } } @@ -35,7 +35,7 @@ public class TellerService { bankAccountService.transfer(fromAccontId, toAccountId, amount); auditService.log(fromAccontId, toAccountId, amount); BigDecimal balance = bankAccountService.balanceOf(fromAccontId); - if(balance.compareTo(BigDecimal.ZERO) <= 0) { + if (balance.compareTo(BigDecimal.ZERO) <= 0) { userTransaction.rollback(); throw new RuntimeException("Insufficient fund."); } else { diff --git a/jta/src/main/java/com/baeldung/jtademo/services/TestHelper.java b/jta/src/main/java/com/baeldung/jtademo/services/TestHelper.java index 7b818b3771..c1e8e355ec 100644 --- a/jta/src/main/java/com/baeldung/jtademo/services/TestHelper.java +++ b/jta/src/main/java/com/baeldung/jtademo/services/TestHelper.java @@ -1,17 +1,14 @@ package com.baeldung.jtademo.services; -import com.baeldung.jtademo.dto.TransferLog; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.datasource.init.ScriptUtils; import org.springframework.stereotype.Component; import javax.sql.DataSource; -import java.math.BigDecimal; import java.sql.Connection; import java.sql.SQLException; @@ -43,5 +40,4 @@ public class TestHelper { } } - } diff --git a/jta/src/test/java/com/baeldung/jtademo/JtaDemoUnitTest.java b/jta/src/test/java/com/baeldung/jtademo/JtaDemoUnitTest.java index 2f72098c41..3f6004262b 100644 --- a/jta/src/test/java/com/baeldung/jtademo/JtaDemoUnitTest.java +++ b/jta/src/test/java/com/baeldung/jtademo/JtaDemoUnitTest.java @@ -1,18 +1,22 @@ package com.baeldung.jtademo; import com.baeldung.jtademo.dto.TransferLog; -import com.baeldung.jtademo.services.*; +import com.baeldung.jtademo.services.AuditService; +import com.baeldung.jtademo.services.BankAccountService; +import com.baeldung.jtademo.services.TellerService; +import com.baeldung.jtademo.services.TestHelper; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.math.BigDecimal; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + @RunWith(SpringRunner.class) @SpringBootTest(classes = JtaDemoApplication.class) public class JtaDemoUnitTest { From 37daee16022d24bdcb7ab249a5b46906bf051202 Mon Sep 17 00:00:00 2001 From: Rudi Adianto Date: Wed, 5 Sep 2018 10:19:35 +0700 Subject: [PATCH 11/11] rollback module jee-7 --- jee-7/src/main/java/com/baeldung/jta/AuditService.java | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 jee-7/src/main/java/com/baeldung/jta/AuditService.java diff --git a/jee-7/src/main/java/com/baeldung/jta/AuditService.java b/jee-7/src/main/java/com/baeldung/jta/AuditService.java deleted file mode 100644 index f9253b9d74..0000000000 --- a/jee-7/src/main/java/com/baeldung/jta/AuditService.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.baeldung.jta; - -public class AuditService { - -}