package com.ankurm.hibernatedemo; import com.ankurm.hibernatedemo.immutable.WideImmutableRow; import com.ankurm.hibernatedemo.immutable.WideMutableRow; import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManagerFactory; import java.util.List; import org.hibernate.Session; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; /** * Backs docs/07-immutable-entities.md, "Does @Immutable actually cost less at flush time?". * *

This is NOT a rigorous benchmark: it is one Spring context, in a shared container, timed * with {@code System.nanoTime()} around a single {@code flush()} call per entity type, no JMH, * no warm-up isolation, no forked JVM. Treat the numbers as indicative of the right order of * magnitude and direction, not as a citable throughput claim. Rows: 4000 per entity type, * 12 String columns each, loaded fully into the persistence context, then flushed with NO * pending changes -- so any time difference is purely Hibernate deciding "does this entity need * an UPDATE", not the cost of writing one. */ @SpringBootTest class ImmutableFlushCostTest { private static final Logger DEMO = LoggerFactory.getLogger("DEMO"); private static final int ROW_COUNT = 4000; @Autowired private EntityManagerFactory emf; @Test void flushingManyLoadedEntities_immutableSkipsDirtyCheck_mutableDoesNot() { seedMutable(ROW_COUNT); seedImmutable(ROW_COUNT); // Warm-up pass (JIT, connection pool, buffer pool) -- discarded. timeMutableFlush(); timeImmutableFlush(); long mutableNanos = timeMutableFlush(); long immutableNanos = timeImmutableFlush(); DEMO.info("flush() over {} loaded MUTABLE rows (12 cols, no pending changes): {} ms", ROW_COUNT, mutableNanos / 1_000_000.0); DEMO.info("flush() over {} loaded @Immutable rows (12 cols, no pending changes): {} ms", ROW_COUNT, immutableNanos / 1_000_000.0); DEMO.info("ratio (mutable / immutable) = {}", (double) mutableNanos / immutableNanos); DEMO.info("CAVEAT: single-run, shared-container timing -- indicative only, not a benchmark result."); } private long timeMutableFlush() { EntityManager em = emf.createEntityManager(); Session session = em.unwrap(Session.class); em.getTransaction().begin(); List rows = session.createQuery("from WideMutableRow", WideMutableRow.class).list(); long start = System.nanoTime(); em.flush(); long elapsed = System.nanoTime() - start; em.getTransaction().rollback(); em.close(); return elapsed; } private long timeImmutableFlush() { EntityManager em = emf.createEntityManager(); Session session = em.unwrap(Session.class); em.getTransaction().begin(); List rows = session.createQuery("from WideImmutableRow", WideImmutableRow.class).list(); long start = System.nanoTime(); em.flush(); long elapsed = System.nanoTime() - start; em.getTransaction().rollback(); em.close(); return elapsed; } private void seedMutable(int count) { EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); for (int i = 0; i < count; i++) { em.persist(new WideMutableRow("seed-" + i)); } em.getTransaction().commit(); em.close(); } private void seedImmutable(int count) { EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); for (int i = 0; i < count; i++) { em.persist(new WideImmutableRow("seed-" + i)); } em.getTransaction().commit(); em.close(); } }