Skip to main content

Hibernate Transactions and Performance in Spring Apps

This guide focuses on the Hibernate-level behaviors that usually cause production issues: transaction boundaries, locking, flush/commit timing, and query performance.

Transaction Basics in Springโ€‹

@Transactional
public void placeOrder() {
orderRepository.save(order);
inventoryService.decrementStock();
}

All operations in the method run in one transaction and either commit together or roll back together.

Transaction Attributesโ€‹

AttributePurpose
readOnlyOptimization hint for read-heavy operations
propagationControls nested transaction behavior
isolationDB isolation level
rollbackForChecked exception rollback control
timeoutTransaction timeout in seconds

Default Rollback Rulesโ€‹

Exception TypeDefault Behavior
RuntimeExceptionRollback
Checked exception (Exception)Commit
ErrorRollback

Spring does not roll back checked exceptions unless configured.

Why This Behavior Existsโ€‹

Historically, checked exceptions represented business conditions while unchecked exceptions represented system failures. In modern systems, this assumption is often not enough, so explicit rollback rules are frequently required.

Force rollback for checked exceptionsโ€‹

@Transactional(rollbackFor = Exception.class)
public void createOrder() throws Exception {
orderRepository.save(order);
throw new Exception("fail");
}

Common Pitfallsโ€‹

Swallowed exceptionsโ€‹

@Transactional
public void createOrder() {
try {
orderRepository.save(order);
throw new RuntimeException();
} catch (Exception e) {
// swallowed
}
}

Result: transaction commits.

Self-invocation bypasses proxyโ€‹

public void methodA() {
methodB();
}

@Transactional
public void methodB() {
throw new RuntimeException();
}

Result: @Transactional on methodB is not applied.

Non-public methodsโ€‹

@Transactional
private void doSomething() {}

Result: ignored by proxy-based AOP.

Transaction Lifecycle (Internal)โ€‹

  1. Proxy intercepts method call.
  2. PlatformTransactionManager starts transaction.
  3. Business logic executes.
  4. Rollback rules are evaluated if exception occurs.
  5. Transaction is committed or rolled back.

Core components:

  • TransactionInterceptor
  • PlatformTransactionManager
  • AOP proxy (JDK/CGLIB)

Flush vs Commitโ€‹

StepMeaning
save()Entity enters persistence context
flush()SQL is generated/sent
commit()Transaction is finalized

Hibernate uses write-behind, so SQL may be deferred until flush/commit.

Propagation and Isolationโ€‹

REQUIREDโ€‹

  • Joins current transaction if one exists.
  • Starts a new one otherwise.

REQUIRES_NEWโ€‹

  • Suspends outer transaction.
  • Starts an independent transaction.

Useful for audit writes and outbox records.

NESTEDโ€‹

  • Uses savepoints.
  • Supports partial rollback semantics in compatible transaction managers.

Isolation levelsโ€‹

LevelPrevents
READ_COMMITTEDDirty reads
REPEATABLE_READNon-repeatable reads
SERIALIZABLEPhantom reads

Lockingโ€‹

Optimistic locking (@Version)โ€‹

@Entity
public class Product {
@Id
private Long id;

private int stock;

@Version
private Long version;
}

Use when conflicts are rare; retry on optimistic lock failure.

Pessimistic locking (SELECT ... FOR UPDATE)โ€‹

@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT p FROM Product p WHERE p.id = :id")
Optional<Product> findByIdForUpdate(@Param("id") Long id);

Use for high-contention resources such as inventory or seat booking.

Practical strategy choiceโ€‹

ScenarioStrategyWhy
Low-contention write pathsOptimisticMinimal lock overhead
High-contention recordsPessimisticStrong conflict control
Read-heavy reportingreadOnly = trueLower overhead
Long-running workflowsOptimistic + retryAvoid long lock hold times

N+1 Query Problemโ€‹

List<User> users = userRepository.findAll();
for (User u : users) {
u.getOrders().size();
}

This can trigger 1 + N queries.

Fix patternsโ€‹

@Query("SELECT u FROM User u JOIN FETCH u.orders")
List<User> findAllWithOrders();
@EntityGraph(attributePaths = {"orders"})
List<User> findAll();

Caching Layersโ€‹

  • First-level cache: per Hibernate Session/transaction.
  • Second-level cache: shared app cache (disabled by default).

Use second-level cache selectively for high-read, low-churn data.

Connection Pool and Batch Tuningโ€‹

For detailed pool configuration, sizing heuristics, and failure modes, see the centralized Database Connection Pooling guide.

spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 20000

jpa:
properties:
hibernate:
jdbc:
batch_size: 50
order_inserts: true
order_updates: true

Guidelines:

  • Keep pool sizes realistic for DB CPU capacity (see Connection Pooling).
  • Enable batching for bulk writes.
  • Monitor slow queries and lock waits continuously.

Senior Design Heuristicsโ€‹

  1. Keep transactions short.
  2. Do not call remote APIs inside DB transactions.
  3. Place transaction boundaries at service layer.
  4. Validate critical endpoint SQL generated by ORM.

Microservices Noteโ€‹

Avoid distributed DB transactions (2PC) as a default pattern. Prefer saga-style coordination and transactional outbox approaches.

TL;DRโ€‹

CaseOutcome
No exceptionCommit
Checked exception (default)Commit
Runtime exceptionRollback
rollbackFor configuredRollback as configured
Swallowed exceptionCommit
Self-invocationTransaction not applied
save() callNot a commit by itself
Commit phaseFinal write durability

Compare Nextโ€‹