Data Persistence with Spring Data JPA
Most microservices need to store state. Spring Data JPA provides a repository abstraction over JPA (Hibernate), significantly reducing boilerplate code.1. Dependencies
Inpom.xml (or build.gradle):
2. Defining Entities
An Entity represents a table in your database.3. The Repository Interface
This is where the magic happens. You don’t need to write implementation classes.4. Service Layer & Transactions
Business logic lives in the Service layer, not the controller.@Transactional Explained
- Atomicity: Either all operations in the method succeed, or none do.
- Rollback: If a
RuntimeExceptionis thrown, the transaction rolls back automatically. - Propagation: If one transactional method calls another, how do they relate? (Default is data joining the existing transaction).
5. H2 Console
When using H2 (in-memory DB), you can view the data in a browser. Add toapplication.properties:
http://localhost:8080/h2-console.
6. Projections
Sometimes you don’t want the full Entity. You just want a slice of data.7. The N+1 Query Problem
This is the most common performance killer in Hibernate. Imagine: 1Author has N Books.
8. Concurrency Control (Locking)
What if two users update the same product price at the exact same millisecond? “Lost Update” problem.Optimistic Locking (Recommended)
Add a@Version field.
UPDATE product SET price = 10, version = 2 WHERE id = 1 AND version = 1.
If the version doesn’t match (someone else updated it), it throws OptimisticLockException.
Pessimistic Locking
Lock the database row.9. Auditing
Keep track of “Who changed what and when” automatically.- Add
@EnableJpaAuditingto main class. - Add fields to Entity:
10. Testing with @DataJpaTest
Don’t use the full@SpringBootTest for DB tests (too slow). Use Slice Testing.
11. JPA Architecture
12. Deep Dive: Transaction Management
Handling transactions correctly is what separates seniors from juniors.Propagation Levels (@Transactional(propagation = ...))
| Level | Description | Use Case |
|---|---|---|
REQUIRED (Default) | Join existing transaction. If none, create new. | Most business logic. |
REQUIRES_NEW | Suspend current transaction. Create a brand new independent one. | Audit logging (save log even if main logic fails). |
MANDATORY | Must be called inside a transaction. Else throw Exception. | Helper methods that shouldn’t run standalone. |
SUPPORTS | Run in transaction if exists. Else run non-transactional. | Read-only operations. |
NOT_SUPPORTED | Suspend current transaction. Run non-transactional. | Sending emails/long processes (don’t hold DB lock). |
NESTED | Create a Savepoint within the existing transaction. | Complex rollbacks (try sub-task, if fail, rollback only sub-task). |
Isolation Levels (@Transactional(isolation = ...))
Defines “how much” one transaction sees of another.
- READ_UNCOMMITTED: Dirty Reads allowed. (Dangerous).
- READ_COMMITTED: PostgreSQL Default. No Dirty Reads.
- REPEATABLE_READ: No Non-Repeatable Reads. (MySQL Default).
- SERIALIZABLE: Full locking. Slowest but safest.
Rollback Rules
By default, Spring ONLY rolls back onRuntimeException (Unchecked).
It does NOT rollback on CheckedException (e.g., IOException).
Fix:
13. High-Performance Caching
Caching is the easiest way to improve performance. Spring provides an abstraction over multiple caching providers.Enable Caching
Basic Usage
@Cacheable: If key exists in cache, return cached value. Else, execute method and cache the result.@CacheEvict: Remove from cache.@CachePut: Always execute method AND update cache.
Using Redis (Production)
By default, Spring usesConcurrentHashMap (in-memory). For distributed systems, use Redis.
Dependency:
Pitfalls
- Serialization Issues: Your cached objects must be
Serializable. Use Jackson for JSON serialization. - Cache Stampede: If cache expires, 1000 requests hit DB at once. Use
@Cacheable(sync = true)(locks during computation). - Stale Data: Always define a TTL (Time to Live).