0% found this document useful (0 votes)
2 views3 pages

Transaction Spring

Database transactions in Hibernate and Spring Boot define a unit of work where all changes succeed or fail together, typically managed with the @Transactional annotation at the service level. Best practices include keeping transactions short, using readOnly for queries, and avoiding mixing database operations with external API calls. Common mistakes involve overusing @Transactional, creating long transactions, and misunderstanding transaction boundaries, which can lead to performance issues and bugs.

Uploaded by

Nithin
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views3 pages

Transaction Spring

Database transactions in Hibernate and Spring Boot define a unit of work where all changes succeed or fail together, typically managed with the @Transactional annotation at the service level. Best practices include keeping transactions short, using readOnly for queries, and avoiding mixing database operations with external API calls. Common mistakes involve overusing @Transactional, creating long transactions, and misunderstanding transaction boundaries, which can lead to performance issues and bugs.

Uploaded by

Nithin
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Database Transactions with Hibernate and Spring

Boot
Database transactions with Hibernate and Spring Boot are not only a persistence detail. They are a design
decision.

A transaction defines a unit of work: either all database changes succeed together, or all of them fail
together.

In Spring Boot, transactions are commonly managed with @Transactional. The annotation should
normally mark the boundary of a business operation, not every individual database call.
@Service
public class OrderService {

private final OrderRepository orderRepository;


private final PaymentRepository paymentRepository;

@Transactional
public void placeOrder(OrderRequest request) {
Order order = [Link](new Order([Link]()));

[Link](new Payment([Link](), [Link]()));

[Link]();
}
}

If saving the payment fails, the order should not remain half-created. That is the point of the transaction.

The transaction usually belongs at the service level because the service method represents the business
use case.

A repository usually knows how to save, update, delete, or query one entity. A service method knows the
full business operation.

A weak design is to put separate transactions around small repository-style operations that are actually part
of the same business use case.
@Transactional
public void saveOrder(Order order) {
[Link](order);
}

@Transactional
public void savePayment(Payment payment) {
[Link](payment);
}

This creates two separate transaction boundaries. If the second operation fails, the first operation may
already be committed.
@Transactional
public void placeOrder(OrderRequest request) {
Order order = [Link](new Order([Link]()));
[Link](new Payment([Link](), [Link]()));
}

One business operation. One transaction. One rollback boundary.

Transaction: a database unit of work.

Commit: making all changes permanent.

Rollback: undoing all changes inside the transaction.

Persistence context: Hibernate's managed context for entities loaded or saved during a transaction.

Dirty checking: Hibernate automatically detects changed managed entities and prepares updates.

Flush: synchronizing Hibernate changes with the database. Flush does not always mean commit.

Propagation: how a method behaves when called inside an existing transaction.

Isolation: how much one transaction can see changes made by another transaction.

Best practices:
- Put @Transactional on service methods that represent business use cases.
- Keep transactions short.
- Use readOnly = true for read-only queries.
- Let exceptions bubble up when rollback is required.
- Be careful with lazy loading outside a transaction.
- Understand when Hibernate flushes changes.
- Use optimistic locking for concurrent updates.
- Separate database transactions from external API calls when possible.
@Transactional(readOnly = true)
public OrderDetails getOrder(Long orderId) {
return [Link](orderId)
.orElseThrow(() -> new OrderNotFoundException(orderId));
}

Common mistakes:
- Putting @Transactional everywhere.
- Opening long transactions around slow network calls.
- Mixing database writes and remote API calls without thinking about failure.
- Assuming save() means the SQL was committed immediately.
- Catching exceptions and hiding them inside a transaction.
- Using transactions to cover bad domain design.
- Depending on lazy loading in controllers.
- Updating large amounts of data entity by entity without batching.
@Transactional
public void processOrder(Long orderId) {
Order order = [Link](orderId).orElseThrow();
[Link](order); // external network call inside transaction

[Link]();
}

This is risky because the database transaction stays open while waiting for a remote system.

That can hold connections longer, increase lock duration, reduce throughput, and make failures harder to
recover from.

A safer design is to persist the local state change in a short transaction, then call the remote system
outside the database transaction, or use an outbox/event-driven flow for stronger reliability.
@Transactional
public void markOrderPendingPayment(Long orderId) {
Order order = [Link](orderId).orElseThrow();
[Link]();
}

Pros:
- Clean business code.
- Declarative transaction boundaries.
- Automatic rollback behavior.
- Good integration with Hibernate and JPA.
- Consistent business operations.
- Less manual commit and rollback code.
Cons:
- Easy to overuse.
- Proxy-based behavior can surprise developers.
- Self-invocation may bypass @Transactional.
- Long transactions can hurt performance.
- Wrong propagation can create confusing bugs.
- Lazy loading problems often appear when transaction boundaries are unclear.
public void outerMethod() {
innerMethod();
}

@Transactional
public void innerMethod() {
// may not start a transaction when called from the same class
}

Spring transactions are usually applied through proxies. Calling a transactional method from the same
class may bypass the proxy.

The useful design question is: what must succeed or fail together?

That is usually the transaction boundary.

You might also like