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
customer
table in the database. - CustomerRepository: A Spring Data JPA repository extending
JpaRepository
for 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 aSELECT
query 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
save
and one forfindById
. - Each transaction uses a separate
EntityManager
(persistence context), which closes after the transaction ends. - A
SELECT
query in thefindById
transaction 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
save
andfindById
run in one transaction, sharing the same persistence context. - Cache Utilization: After
save
persists the entity, it’s cached in the persistence context. The subsequentfindById
retrieves it from the cache without issuing aSELECT
query.
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 indicatessave
andfindById
share the same context. - No
SELECT
query 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.