package com.descartes.hibhik; import static org.junit.Assert.*; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityTransaction; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.descartes.hibhik.db.BatchTestTable; import com.descartes.hibhik.db.TestTable; import com.zaxxer.hikari.HikariPoolJmx; /** * Test various database CRUD operations and check that all connections are returned to pool afterwards. * @author FMartian * */ public class TestDbCrud { /** * Batch insert only works when hibernate.jdbc.batch_size is set AND the record has no auto-generated ID. * This value corresponds with the hibernate.jdbc.batch_size in the propertries files. */ public static final int BATCH_SIZE = 5; private static final Logger log = LoggerFactory.getLogger(TestDbCrud.class); private static Emf emf; private static HikariPoolJmx pool; @BeforeClass public static void openDb() { emf = new Emf(); emf.init(); pool = new HikariPoolJmx(Emf.UNIT_NAME); } @AfterClass public static void closeDb() { if (emf != null) { emf.close(); } } private Long recordId, recordId2; @Test public void dbCrud() { log.debug("insert a record"); dbTx(new DbAction() { @Override public void toDb(EntityManager em) { TestTable tr = new TestTable(); tr.setName("Marvin"); em.persist(tr); } }); log.debug("list all records"); db(new DbAction() { @Override public void toDb(EntityManager em) { List<TestTable> trs = em.createQuery("select tr from TestTable tr", TestTable.class).getResultList(); assertEquals("One record", 1, trs.size()); recordId = trs.get(0).getId(); log.debug("First record ID is " + recordId); } }); // JPA typesafe way of selecting all records - horribly and overly complex. // Only here for reference. // Copied from http://www.adam-bien.com/roller/abien/entry/selecting_all_jpa_entities_as log.debug("list all records typesafe"); db(new DbAction() { @Override public void toDb(EntityManager em) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<TestTable> cq = cb.createQuery(TestTable.class); Root<TestTable> rootEntry = cq.from(TestTable.class); CriteriaQuery<TestTable> all = cq.select(rootEntry); TypedQuery<TestTable> allQuery = em.createQuery(all); List<TestTable> trs = allQuery.getResultList(); assertEquals("One record", 1 , trs.size()); recordId = trs.get(0).getId(); } }); log.debug("find by ID"); db(new DbAction() { @Override public void toDb(EntityManager em) { TestTable tr = em.find(TestTable.class, recordId); assertEquals("Marvin", tr.getName()); } }); log.debug("update name"); dbTx(new DbAction() { @Override public void toDb(EntityManager em) { TestTable tr = em.find(TestTable.class, recordId); tr.setName("Marvin Martian"); } }); log.debug("verify updated name"); db(new DbAction() { @Override public void toDb(EntityManager em) { TestTable tr = em.find(TestTable.class, recordId); assertEquals("Marvin Martian", tr.getName()); } }); log.debug("insert with flush"); dbTx(new DbAction() { @Override public void toDb(EntityManager em) { TestTable tr = new TestTable(); tr.setName("Record 2"); em.persist(tr); em.flush(); // flush requires a connection to be used assertEquals("After flush", 1, pool.getActiveConnections()); recordId2 = tr.getId(); log.debug("Second record ID is " + recordId2); assertEquals("Flush should get the auto-generated value.", (Long)(recordId + 1L), recordId2); } }); log.debug("fail an update"); dbRollback(new DbAction() { @Override public void toDb(EntityManager em) { TestTable tr = em.find(TestTable.class, recordId2); tr.setName("Record 3"); } }); log.debug("verify update failed"); db(new DbAction() { @Override public void toDb(EntityManager em) { TestTable tr = em.find(TestTable.class, recordId2); assertEquals("Record 2", tr.getName()); } }); log.debug("delete first record by ID, without 'finding' the record"); dbTx(new DbAction() { @Override public void toDb(EntityManager em) { int deleted = em.createQuery("delete from TestTable tr where tr.id = :id").setParameter("id", recordId).executeUpdate(); assertEquals("One record deleted.", 1 , deleted); } }); log.debug("verify delete of first record"); db(new DbAction() { @Override public void toDb(EntityManager em) { List<TestTable> trs = em.createQuery("select tr from TestTable tr", TestTable.class).getResultList(); assertEquals("One record", 1, trs.size()); assertEquals("Only record 2 left", recordId2, trs.get(0).getId()); } }); log.debug("delete second record by ID, 'finding' the record first"); dbTx(new DbAction() { @Override public void toDb(EntityManager em) { TestTable tr = em.find(TestTable.class, recordId2); em.remove(tr); } }); log.debug("verify no records in table left"); db(new DbAction() { @Override public void toDb(EntityManager em) { List<TestTable> trs = em.createQuery("select tr from TestTable tr", TestTable.class).getResultList(); assertEquals("No records", 0, trs.size()); } }); log.debug("batch insert"); dbTx(new DbAction() { @Override public void toDb(EntityManager em) { // Follow the idiom from Hibernate documented at: // http://docs.jboss.org/hibernate/core/4.3/manual/en-US/html/ch15.html#batch-inserts for (int i = 1; i < 2 * BATCH_SIZE + 1; i++) { BatchTestTable tr = new BatchTestTable(); tr.setId((long) i); tr.setName("Batchname" + i); em.persist(tr); if (i % BATCH_SIZE == 0) { em.flush(); em.clear(); } } } }); log.debug("verify batch insert"); db(new DbAction() { @Override public void toDb(EntityManager em) { List<BatchTestTable> trs = em.createQuery("select tr from BatchTestTable tr", BatchTestTable.class).getResultList(); assertEquals(2 * BATCH_SIZE + " records", 2 * BATCH_SIZE, trs.size()); } }); } private void db(DbAction a) { assertEquals("Before query", 0, pool.getActiveConnections()); EntityManager em = emf.openSession(); try { a.toDb(em); } finally { em.close(); } assertEquals("After query", 0, pool.getActiveConnections()); } /** * Uses the idiom as specified in: * <br>http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html/ch13.html#transactions-demarcation-nonmanaged * @param a */ private void dbTx(DbAction a) { assertEquals("Before tx", 0, pool.getActiveConnections()); EntityManager em = emf.openSession(); EntityTransaction tx = null; try { tx = em.getTransaction(); tx.begin(); a.toDb(em); tx.commit(); } catch (RuntimeException e) { if (tx != null) tx.rollback(); throw e; } finally { em.close(); } assertEquals("After tx", 0, pool.getActiveConnections()); } private void dbRollback(DbAction a) { assertEquals("Before rollback", 0, pool.getActiveConnections()); EntityManager em = emf.openSession(); EntityTransaction tx = null; try { tx = em.getTransaction(); tx.begin(); a.toDb(em); throw new RuntimeException("Rollback test."); //tx.commit(); } catch (RuntimeException e) { if (tx != null) tx.rollback(); log.debug("Tx rolled back: " + e); } finally { em.close(); assertEquals("After rollback", 0, pool.getActiveConnections()); } } /* * Run this test 'manually'. * This test sets a random database-password which will fail other tests. */ // @Test public void checkJmxCalls() { try { callJmxFunctions(); } catch (Exception e) { e.printStackTrace(); fail(); } } static void callJmxFunctions() { log.debug("Testing JMX calls"); pool.getActiveConnections(); pool.getConnectionTimeout(); pool.getIdleConnections(); pool.getLeakDetectionThreshold(); pool.getMaximumPoolSize(); pool.getMaxLifetime(); pool.getMinimumIdle(); pool.getPoolName(); pool.getThreadsAwaitingConnection(); pool.getTotalConnections(); pool.getValidationTimeout(); pool.suspendPool(); pool.resumePool(); pool.setConnectionTimeout(1000); pool.setIdleTimeout(1000); pool.setLeakDetectionThreshold(10000); pool.setMaximumPoolSize(10); pool.setMaxLifetime(100000); pool.setMinimumIdle(2); pool.setValidationTimeout(1000); pool.softEvictConnections(); pool.setUsername("test"); pool.setPassword("bla"); log.debug("Done testing JMX calls"); } }