This project demonstrates how Hibernate's first-level cache operates within a Spring Boot application using JPA. It includes two endpoints that highlight the cache's behavior: one where the cache is not utilized due to separate transactions, and another where it is effectively used within a single transaction.
- CustomerEntity: A JPA entity representing the
customertable in the database. - CustomerRepository: A Spring Data JPA repository extending
JpaRepositoryfor CRUD operations onCustomerEntity. - CustomerController: A REST controller with two endpoints to showcase first-level cache behavior:
/without-first-level-cache: Shows the absence of caching when repository methods run in separate transactions (Sessions)./with-first-level-cache: Demonstrates caching within a single transaction (same session) using@Transactional.
This endpoint calls save and findById from CustomerRepository without an explicit @Transactional annotation on the controller method. As a result, each repository method executes in its own transaction, each with a separate persistence context.
- Transaction for
save: A new transaction begins, persists the customer entity to the database, and caches it in the persistence context. The transaction then commits and closes, ending the persistence context. - Transaction for
findById: A new transaction starts with its own persistence context. Since it doesn’t share the previous context, the entity isn’t available in the cache, and aSELECTquery fetches it from the database.
2025-03-19T21:54:51.184+03:30 DEBUG 1289930 --- [jpa-test] [nio-8080-exec-6] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2025-03-19T21:54:51.184+03:30 DEBUG 1289930 --- [jpa-test] [nio-8080-exec-6] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(810818063<open>)] for JPA transaction
2025-03-19T21:54:51.184+03:30 DEBUG 1289930 --- [jpa-test] [nio-8080-exec-6] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@3b283604]
2025-03-19T21:54:51.185+03:30 DEBUG 1289930 --- [jpa-test] [nio-8080-exec-6] org.hibernate.SQL : /* insert for dev.hamze.jpa.test.CustomerEntity */insert into customer (date_of_birth,first_name,last_name,id) values (?,?,?,default)
2025-03-19T21:54:51.185+03:30 DEBUG 1289930 --- [jpa-test] [nio-8080-exec-6] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit
2025-03-19T21:54:51.186+03:30 DEBUG 1289930 --- [jpa-test] [nio-8080-exec-6] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(810818063<open>)]
2025-03-19T21:54:51.186+03:30 DEBUG 1289930 --- [jpa-test] [nio-8080-exec-6] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(810818063<open>)] after transaction
2025-03-19T21:54:51.186+03:30 INFO 1289930 --- [jpa-test] [nio-8080-exec-6] dev.hamze.jpa.test.CustomerController : Customer saved without first level cache: CustomerEntity(customerId=5, firstName=John, lastName=Doe, dateOfBirth=2025-03-19)
2025-03-19T21:54:51.186+03:30 DEBUG 1289930 --- [jpa-test] [nio-8080-exec-6] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findById]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly
2025-03-19T21:54:51.186+03:30 DEBUG 1289930 --- [jpa-test] [nio-8080-exec-6] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(439371427<open>)] for JPA transaction
2025-03-19T21:54:51.186+03:30 DEBUG 1289930 --- [jpa-test] [nio-8080-exec-6] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@5a909c5d]
2025-03-19T21:54:51.187+03:30 DEBUG 1289930 --- [jpa-test] [nio-8080-exec-6] org.hibernate.SQL : select ce1_0.id,ce1_0.date_of_birth,ce1_0.first_name,ce1_0.last_name from customer ce1_0 where ce1_0.id=?
2025-03-19T21:54:51.187+03:30 DEBUG 1289930 --- [jpa-test] [nio-8080-exec-6] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit
2025-03-19T21:54:51.187+03:30 DEBUG 1289930 --- [jpa-test] [nio-8080-exec-6] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(439371427<open>)]
2025-03-19T21:54:51.187+03:30 DEBUG 1289930 --- [jpa-test] [nio-8080-exec-6] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(439371427<open>)] after transaction
2025-03-19T21:54:51.187+03:30 INFO 1289930 --- [jpa-test] [nio-8080-exec-6] dev.hamze.jpa.test.CustomerController : Customer found without first level cache: Optional[CustomerEntity(customerId=5, firstName=John, lastName=Doe, dateOfBirth=2025-03-19)]
- Key Observations:
- Two distinct transactions are created: one for
saveand one forfindById. - Each transaction uses a separate
EntityManager(persistence context), which closes after the transaction ends. - A
SELECTquery in thefindByIdtransaction confirms the entity wasn’t retrieved from the cache.
- Two distinct transactions are created: one for
This endpoint is annotated with @Transactional, ensuring that save and findById execute within the same transaction and share a single persistence context.
- Single Transaction: Both
saveandfindByIdrun in one transaction, sharing the same persistence context. - Cache Utilization: After
savepersists the entity, it’s cached in the persistence context. The subsequentfindByIdretrieves it from the cache without issuing aSELECTquery.
2025-03-19T21:56:01.566+03:30 DEBUG 1289930 --- [jpa-test] [io-8080-exec-10] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [dev.hamze.jpa.test.CustomerController.withFirstLevelCache]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2025-03-19T21:56:01.566+03:30 DEBUG 1289930 --- [jpa-test] [io-8080-exec-10] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(2067982765<open>)] for JPA transaction
2025-03-19T21:56:01.566+03:30 DEBUG 1289930 --- [jpa-test] [io-8080-exec-10] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@62535865]
2025-03-19T21:56:01.566+03:30 DEBUG 1289930 --- [jpa-test] [io-8080-exec-10] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(2067982765<open>)] for JPA transaction
2025-03-19T21:56:01.566+03:30 DEBUG 1289930 --- [jpa-test] [io-8080-exec-10] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction
2025-03-19T21:56:01.567+03:30 DEBUG 1289930 --- [jpa-test] [io-8080-exec-10] org.hibernate.SQL : /* insert for dev.hamze.jpa.test.CustomerEntity */insert into customer (date_of_birth,first_name,last_name,id) values (?,?,?,default)
2025-03-19T21:56:01.567+03:30 INFO 1289930 --- [jpa-test] [io-8080-exec-10] dev.hamze.jpa.test.CustomerController : Customer saved with first level cache: CustomerEntity(customerId=6, firstName=John, lastName=Doe, dateOfBirth=2025-03-19)
2025-03-19T21:56:01.567+03:30 DEBUG 1289930 --- [jpa-test] [io-8080-exec-10] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(2067982765<open>)] for JPA transaction
2025-03-19T21:56:01.567+03:30 DEBUG 1289930 --- [jpa-test] [io-8080-exec-10] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction
2025-03-19T21:56:01.568+03:30 INFO 1289930 --- [jpa-test] [io-8080-exec-10] dev.hamze.jpa.test.CustomerController : Customer found with first level cache: Optional[CustomerEntity(customerId=6, firstName=John, lastName=Doe, dateOfBirth=2025-03-19)]
2025-03-19T21:56:01.568+03:30 DEBUG 1289930 --- [jpa-test] [io-8080-exec-10] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit
2025-03-19T21:56:01.568+03:30 DEBUG 1289930 --- [jpa-test] [io-8080-exec-10] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(2067982765<open>)]
2025-03-19T21:56:01.568+03:30 DEBUG 1289930 --- [jpa-test] [io-8080-exec-10] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(2067982765<open>)] after transaction
- Key Observations:
- One transaction spans the entire method, using a single
EntityManager. - The
"Participating in existing transaction"log indicatessaveandfindByIdshare the same context. - No
SELECTquery is logged forfindById, proving the entity was retrieved from the cache.
- One transaction spans the entire method, using a single
This project highlights the role of transactional context in Hibernate’s first-level cache:
- Without
@Transactional: Separate transactions create isolated persistence contexts, preventing cache reuse and requiring database queries. - With
@Transactional: A shared transaction enables the same persistence context, allowing the cache to optimize performance by avoiding redundant queries.