Skip to main content

Week 5 Day 3 — Transactions Deep Dive

Goal

Today I want to understand transactions in Spring Data JPA.

Main questions:

  1. What is a transaction?
  2. Why do we need transactions?
  3. What does @Transactional do?
  4. Where should I put @Transactional?
  5. What is the persistence context?
  6. What is dirty checking?
  7. What causes rollback?
  8. What is the difference between checked and unchecked exceptions?
  9. What does readOnly = true mean?
  10. What are common transaction traps?

1. Quick Review from Week 5 Day 2

In Day 2, I learned:

  • Spring Data JPA can create derived queries from repository method names.
  • Repository method names use entity property names, not database column names.
  • @Query can define JPQL or native SQL queries.
  • JPQL uses entity names and fields.
  • Native SQL uses table names and columns.
  • Pageable supports pagination.
  • Page includes total count.
  • Slice is lighter than Page.
  • @Modifying is needed for update/delete queries.

Memory sentence:

Repository methods query entities through Spring Data JPA.

Today I learn how changes are committed or rolled back.


2. What Is a Transaction?

A transaction is a unit of work.

Simple definition:

A transaction groups database operations together so they either all succeed or all fail.

Example:

1. Create invoice
2. Create invoice items
3. Update client balance
4. Write audit log

If step 3 fails, I usually do not want steps 1 and 2 to stay saved.

I want:

all operations succeed

or:

all operations roll back

Memory sentence:

A transaction protects consistency by making a group of operations succeed or fail together.


3. Why Do We Need Transactions?

Without transactions, data can become inconsistent.

Example: money transfer

1. Subtract 100€ from Account A
2. Add 100€ to Account B

If step 1 succeeds but step 2 fails:

money disappears

With a transaction:

if both steps succeed -> commit
if one step fails -> rollback

Memory sentence:

Transactions protect the database from half-finished changes.


4. ACID

Transactions are often described with ACID.

LetterMeaningSimple explanation
AAtomicityall or nothing
CConsistencydata remains valid
IIsolationtransactions do not break each other
DDurabilitycommitted data stays saved

For certification, the most important word is:

Atomicity = all or nothing

5. What Is @Transactional?

@Transactional tells Spring:

Run this method inside a transaction.

Example:

@Service
public class TaskService {

private final TaskRepository taskRepository;

public TaskService(TaskRepository taskRepository) {
this.taskRepository = taskRepository;
}

@Transactional
public TaskDto create(CreateTaskRequest request) {
TaskEntity task = new TaskEntity(request.title(), "OPEN");
TaskEntity saved = taskRepository.save(task);
return toDto(saved);
}
}

Spring starts a transaction before the method and commits it after the method succeeds.

If the method fails with a rollback-triggering exception, Spring rolls back the transaction.

Memory sentence:

@Transactional creates a transaction boundary around a method.


6. Transaction Boundary

Transaction boundary means:

where the transaction starts and where it ends

Example:

@Transactional
public void completeTask(Long taskId) {
TaskEntity task = taskRepository.findById(taskId).orElseThrow();
task.complete();
}

Boundary:

transaction starts before completeTask()
transaction commits after completeTask() returns successfully
transaction rolls back if completeTask() throws rollback exception

Memory sentence:

A transaction boundary wraps a unit of work.


7. Where Should @Transactional Usually Go?

Usually on service methods.

Good:

@Service
public class TaskService {

@Transactional
public TaskDto completeTask(Long id) {
TaskEntity task = taskRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Task", id));

task.complete();

return toDto(task);
}
}

Why service layer?

service contains business operation
service may call multiple repositories
service defines the unit of work
controller should stay HTTP-focused
repository should stay persistence-focused

Memory sentence:

Put transactions around business use cases, usually in the service layer.


8. Controller Should Usually Not Define Transactions

Less ideal:

@RestController
public class TaskController {

@Transactional
@PostMapping("/api/tasks/{id}/complete")
public TaskDto complete(@PathVariable Long id) {
return taskService.completeTask(id);
}
}

Better:

@RestController
public class TaskController {

@PostMapping("/api/tasks/{id}/complete")
public TaskDto complete(@PathVariable Long id) {
return taskService.completeTask(id);
}
}

Service:

@Service
public class TaskService {

@Transactional
public TaskDto completeTask(Long id) {
// business transaction here
}
}

Why?

controller handles HTTP
service handles business transaction

9. Repository Methods Already Have Transactions?

Spring Data repository methods often already have transaction behavior.

For example:

taskRepository.save(task);

can run transactionally.

But in real apps, service methods should still define transactions for business use cases.

Why?

one service method may call multiple repository methods
all operations should commit or rollback together
service defines business unit of work

Example:

@Transactional
public InvoiceDto createInvoice(CreateInvoiceRequest request) {
ClientEntity client = clientRepository.findById(request.clientId()).orElseThrow();
InvoiceEntity invoice = invoiceRepository.save(new InvoiceEntity(client));
auditLogRepository.save(new AuditLogEntity("INVOICE_CREATED"));
return toDto(invoice);
}

All database changes belong to one transaction.


10. What Happens Internally with @Transactional?

Spring uses AOP proxies.

Simplified flow:

1. Client calls service method.
2. Call goes through Spring proxy.
3. Proxy starts transaction.
4. Real service method runs.
5. If method returns normally, proxy commits transaction.
6. If method throws rollback exception, proxy rolls back transaction.

Memory sentence:

@Transactional works through Spring AOP proxy.


11. Transaction Proxy Example

I write:

@Service
public class TaskService {

@Transactional
public void completeTask(Long id) {
// business logic
}
}

Spring creates a proxy around it:

TaskService proxy

starts transaction

calls real TaskService.completeTask()

commits or rolls back

This is why some traps happen, especially self-invocation.


12. Self-Invocation Trap

Bad:

@Service
public class TaskService {

public void outerMethod(Long id) {
completeTask(id);
}

@Transactional
public void completeTask(Long id) {
// transactional logic
}
}

Problem:

outerMethod() calls completeTask() inside the same class.
The call does not go through the Spring proxy.
@Transactional may not be applied.

Memory sentence:

@Transactional works when the call goes through the Spring proxy.

Better:

put @Transactional on outer public service method
move transactional method to another service
call through another Spring bean

Best simple fix:

@Transactional
public void outerMethod(Long id) {
completeTaskInternal(id);
}

private void completeTaskInternal(Long id) {
// logic
}

13. Method Visibility Trap

@Transactional is normally used on public methods.

Good:

@Transactional
public void completeTask(Long id) {
}

Risky/confusing:

@Transactional
private void completeTask(Long id) {
}

Because Spring proxy-based transaction management usually applies to externally called methods through the proxy.

Exam-safe sentence:

Put @Transactional on public service methods that are called from outside the bean.


14. Persistence Context

Persistence context is one of the most important JPA concepts.

Simple definition:

The persistence context is a first-level cache and tracking area for managed entities inside a transaction.

When JPA loads an entity inside a transaction, the entity becomes managed.

Example:

@Transactional
public TaskDto completeTask(Long id) {
TaskEntity task = taskRepository.findById(id).orElseThrow();

task.complete();

return toDto(task);
}

Inside the transaction:

task is managed by the persistence context

JPA tracks changes to it.

Memory sentence:

Persistence context tracks managed entities.


15. Managed Entity

A managed entity is an entity currently tracked by the persistence context.

Example:

TaskEntity task = taskRepository.findById(id).orElseThrow();

Inside a transaction, this entity is usually managed.

If I change it:

task.complete();

JPA notices the change.

At commit time, JPA can update the database.

Memory sentence:

Managed entity changes can be saved automatically at commit time.


16. Dirty Checking

Dirty checking means:

JPA automatically detects changes to managed entities and updates the database at flush/commit time.

Example:

@Transactional
public void completeTask(Long id) {
TaskEntity task = taskRepository.findById(id).orElseThrow();
task.complete();
}

No explicit save() call.

Still, database can be updated because:

task is managed
JPA tracks changes
transaction commits
dirty checking detects change
SQL update is executed

Memory sentence:

Dirty checking saves changes to managed entities without calling save() again.


17. Dirty Checking Example

Entity:

@Entity
@Table(name = "tasks")
public class TaskEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String status;

public void complete() {
this.status = "DONE";
}
}

Service:

@Transactional
public void completeTask(Long id) {
TaskEntity task = taskRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Task", id));

task.complete();
}

Possible SQL at commit:

update tasks set status = 'DONE' where id = ?

Even though I did not call:

taskRepository.save(task);

18. Do I Need save() After Updating a Managed Entity?

Inside a transaction:

@Transactional
public void updateTitle(Long id, String title) {
TaskEntity task = taskRepository.findById(id).orElseThrow();
task.changeTitle(title);
}

Usually no save() is needed.

Why?

task is managed
dirty checking tracks changes
commit flushes changes

But for new entities:

TaskEntity task = new TaskEntity("New task", "OPEN");
taskRepository.save(task);

You need save() to persist the new entity.

Memory sentence:

New entity needs save(). Managed existing entity can update through dirty checking.


19. Flush

Flush means:

Synchronize persistence context changes with the database.

Flush can happen:

before transaction commit
before certain queries
when calling flush()
when repository saveAndFlush() is used

Important:

Flush sends SQL to the database, but commit makes it final.

Memory sentence:

Flush sends changes. Commit finalizes changes.


20. Commit vs Rollback

Commit:

make transaction changes permanent

Rollback:

undo transaction changes

Example:

@Transactional
public void createTaskAndFail() {
taskRepository.save(new TaskEntity("A", "OPEN"));
throw new RuntimeException("fail");
}

Because a runtime exception is thrown:

transaction rolls back
task is not saved permanently

21. Default Rollback Rules

By default, Spring rolls back on:

RuntimeException
Error

By default, Spring does not roll back on:

checked Exception

Example rollback:

@Transactional
public void createTask() {
taskRepository.save(new TaskEntity("A", "OPEN"));
throw new IllegalStateException("Something failed");
}

IllegalStateException is a RuntimeException.

Result:

rollback

Memory sentence:

By default, runtime exceptions roll back; checked exceptions do not.


22. Checked vs Unchecked Exceptions

Unchecked exception:

RuntimeException

Examples:

IllegalArgumentException
IllegalStateException
NullPointerException
ResourceNotFoundException if it extends RuntimeException

Checked exception:

Exception

but not RuntimeException.

Example:

IOException

Checked exceptions must usually be declared or caught.


23. Checked Exception Trap

Example:

@Transactional
public void importTasks() throws IOException {
taskRepository.save(new TaskEntity("A", "OPEN"));

throw new IOException("file failed");
}

Default result:

transaction may commit

Why?

IOException is a checked exception.
Spring does not roll back checked exceptions by default.

Fix:

@Transactional(rollbackFor = IOException.class)
public void importTasks() throws IOException {
taskRepository.save(new TaskEntity("A", "OPEN"));
throw new IOException("file failed");
}

Memory sentence:

Use rollbackFor if a checked exception should roll back.


24. rollbackFor

Example:

@Transactional(rollbackFor = Exception.class)
public void runImport() throws Exception {
// database changes
throw new Exception("fail");
}

This tells Spring:

roll back for Exception too

More specific is often better:

@Transactional(rollbackFor = ImportFailedException.class)

Avoid making everything too broad without thinking.


25. noRollbackFor

Sometimes I do not want rollback for a specific exception.

Example:

@Transactional(noRollbackFor = NotificationFailedException.class)
public void createTaskAndNotify() {
taskRepository.save(new TaskEntity("A", "OPEN"));

notificationService.send();

// if notification fails, maybe task should still be saved
}

Meaning:

Do not roll back for NotificationFailedException.

Use carefully.

Memory sentence:

rollbackFor adds rollback rules. noRollbackFor excludes rollback rules.


26. Catching Exceptions Trap

Bad:

@Transactional
public void createTask() {
try {
taskRepository.save(new TaskEntity("A", "OPEN"));
externalCall();
} catch (RuntimeException ex) {
log.warn("Ignored error", ex);
}
}

Problem:

exception is caught and not rethrown
method returns normally
Spring commits the transaction

If I want rollback, I must:

rethrow the exception
or mark transaction rollback-only

Better:

@Transactional
public void createTask() {
taskRepository.save(new TaskEntity("A", "OPEN"));
externalCall();
}

or:

@Transactional
public void createTask() {
try {
taskRepository.save(new TaskEntity("A", "OPEN"));
externalCall();
} catch (RuntimeException ex) {
log.warn("Error", ex);
throw ex;
}
}

Memory sentence:

If you catch and swallow the exception, Spring may commit.


27. readOnly = true

Example:

@Transactional(readOnly = true)
public TaskDto findById(Long id) {
TaskEntity task = taskRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Task", id));

return toDto(task);
}

Meaning:

This method is intended only to read data.

Benefits:

can be optimization hint
can improve performance in some cases
communicates intent
helps avoid accidental write logic

Important:

readOnly = true is mainly a hint.
It is not always a strict guarantee that writes are impossible.

Memory sentence:

readOnly = true means this transaction is intended for reading.


28. Read-Only Transaction Trap

Bad:

@Transactional(readOnly = true)
public void completeTask(Long id) {
TaskEntity task = taskRepository.findById(id).orElseThrow();
task.complete();
}

Problem:

method modifies data but transaction is marked read-only
behavior may be provider/database dependent
changes may not flush as expected
this is wrong design

Correct:

@Transactional
public void completeTask(Long id) {
TaskEntity task = taskRepository.findById(id).orElseThrow();
task.complete();
}

Memory sentence:

Do not modify data inside read-only transactions.


29. Read Methods vs Write Methods

Good service style:

@Transactional(readOnly = true)
public TaskDto findById(Long id) {
TaskEntity task = taskRepository.findById(id).orElseThrow();
return toDto(task);
}

@Transactional
public TaskDto create(CreateTaskRequest request) {
TaskEntity task = new TaskEntity(request.title(), "OPEN");
return toDto(taskRepository.save(task));
}

@Transactional
public void complete(Long id) {
TaskEntity task = taskRepository.findById(id).orElseThrow();
task.complete();
}

Memory sentence:

Read methods can be read-only. Write methods should be read-write.


30. Class-Level @Transactional

You can put @Transactional on a class.

Example:

@Service
@Transactional(readOnly = true)
public class TaskService {

public TaskDto findById(Long id) {
// read-only transaction
}

@Transactional
public TaskDto create(CreateTaskRequest request) {
// overrides class-level readOnly
}
}

Meaning:

all methods are read-only by default
write methods override with @Transactional

This is a common style.

Memory sentence:

Class-level @Transactional gives defaults; method-level overrides.


31. Propagation

Propagation controls what happens if a transactional method is called when a transaction already exists.

Default:

Propagation.REQUIRED

Meaning:

join existing transaction if one exists
otherwise start a new transaction

Example:

@Transactional
public void createInvoice() {
saveInvoice();
saveAuditLog();
}

If saveInvoice() and saveAuditLog() also use REQUIRED, they join the same transaction.

Memory sentence:

Default propagation is REQUIRED.


32. Common Propagation Types

PropagationSimple meaning
REQUIREDjoin existing or create new
REQUIRES_NEWsuspend existing and start new transaction
SUPPORTSjoin if exists, otherwise run without transaction
MANDATORYmust have existing transaction
NOT_SUPPORTEDrun without transaction
NEVERfail if transaction exists
NESTEDnested transaction with savepoint if supported

For most normal service methods:

REQUIRED is enough

33. REQUIRES_NEW

Example:

@Transactional
public void createTask() {
taskRepository.save(new TaskEntity("A", "OPEN"));
auditService.writeAuditLog("TASK_CREATED");
throw new RuntimeException("main operation failed");
}

Audit service:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void writeAuditLog(String message) {
auditLogRepository.save(new AuditLogEntity(message));
}

Meaning:

audit log runs in its own transaction
main transaction rollback does not necessarily roll back audit transaction

Use carefully.

Memory sentence:

REQUIRES_NEW creates an independent transaction.


34. Isolation

Isolation controls how transactions see each other’s changes.

Common isolation levels:

DEFAULT
READ_UNCOMMITTED
READ_COMMITTED
REPEATABLE_READ
SERIALIZABLE

Most apps use:

Isolation.DEFAULT

which means:

use database default isolation level

For many business apps, database default is enough.

Memory sentence:

Isolation controls visibility between concurrent transactions.


35. Timeout

Timeout defines max transaction duration.

Example:

@Transactional(timeout = 5)
public void runShortOperation() {
// must finish within 5 seconds
}

If transaction takes too long:

transaction may be rolled back

Use for operations that should not hang too long.


36. Transaction and External Calls

Be careful with external API calls inside transactions.

Bad:

@Transactional
public void createTask(CreateTaskRequest request) {
TaskEntity task = taskRepository.save(new TaskEntity(request.title(), "OPEN"));
externalApi.notify(task.getId());
}

Problem:

transaction stays open during network call
network call can be slow
database locks may be held longer
external system may succeed but DB transaction may roll back later

Better options:

commit database change first, then publish event
use outbox pattern
use async processing
use transaction synchronization carefully

Memory sentence:

Keep transactions short and avoid slow external calls inside them.


37. Transaction and Lazy Loading Preview

Lazy relationships often need an open persistence context.

Example:

@Transactional(readOnly = true)
public ClientDto getClient(Long id) {
ClientEntity client = clientRepository.findById(id).orElseThrow();

// accessing lazy relationships may work inside transaction
int taskCount = client.getTasks().size();

return toDto(client, taskCount);
}

Outside transaction, lazy loading can fail with a lazy initialization error.

We will study relationships and lazy loading later.

Memory sentence:

Lazy loading usually needs an open persistence context.


38. Open Session in View Warning

Spring Boot web apps may keep the persistence context open during web request rendering if Open Session in View is enabled.

This can hide lazy loading problems.

But it can also lead to:

database access during view/API serialization
N+1 query problems
less clear transaction boundaries
performance surprises

Good design:

load needed data in service layer
map to DTO inside transaction
return DTO to controller

Memory sentence:

Do not rely on lazy loading during JSON serialization.


39. Transactional Tests

In Spring tests, @Transactional has special behavior.

Example:

@SpringBootTest
@Transactional
class TaskServiceTest {

@Test
void createsTask() {
taskService.create(new CreateTaskRequest("A"));
}
}

In many Spring test setups:

transaction rolls back after test

This keeps database clean.

Memory sentence:

Transactional tests often roll back by default.


40. Full Example: Create Task

Service:

@Service
public class TaskService {

private final TaskRepository taskRepository;

public TaskService(TaskRepository taskRepository) {
this.taskRepository = taskRepository;
}

@Transactional
public TaskDto create(CreateTaskRequest request) {
TaskEntity task = new TaskEntity(request.title(), "OPEN");

TaskEntity saved = taskRepository.save(task);

return toDto(saved);
}

private TaskDto toDto(TaskEntity entity) {
return new TaskDto(
entity.getId(),
entity.getTitle(),
entity.getStatus()
);
}
}

What happens:

1. Transaction starts.
2. New entity is created.
3. save() persists entity.
4. Method returns DTO.
5. Transaction commits.
6. Insert becomes permanent.

41. Full Example: Update Task with Dirty Checking

Service:

@Transactional
public TaskDto complete(Long id) {
TaskEntity task = taskRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Task", id));

task.complete();

return toDto(task);
}

No save() call.

What happens:

1. Transaction starts.
2. Entity is loaded and becomes managed.
3. Entity status changes to DONE.
4. Dirty checking detects change.
5. SQL update runs before commit.
6. Transaction commits.

42. Full Example: Rollback

Service:

@Transactional
public void createTwoTasksAndFail() {
taskRepository.save(new TaskEntity("Task A", "OPEN"));
taskRepository.save(new TaskEntity("Task B", "OPEN"));

throw new RuntimeException("failure");
}

Result:

Task A is rolled back.
Task B is rolled back.
Nothing is permanently saved.

Because:

RuntimeException triggers rollback by default.

43. Full Example: Read-Only Query

Service:

@Transactional(readOnly = true)
public Page<TaskDto> findOpenTasks(int page, int size) {
Pageable pageable = PageRequest.of(
page,
size,
Sort.by(Sort.Direction.DESC, "createdAt")
);

return taskRepository.findByStatus("OPEN", pageable)
.map(this::toDto);
}

Meaning:

read-only transaction
query tasks
map entities to DTOs
no data modifications intended

44. Common Exam Traps

Trap 1

@Transactional usually belongs on service methods, not controllers.


Trap 2

@Transactional works through Spring AOP proxies.


Trap 3

Self-invocation can bypass the transaction proxy.


Trap 4

Private/internal method calls are not a good place for proxy-based @Transactional.


Trap 5

Default propagation is REQUIRED.


Trap 6

Default rollback happens for RuntimeException and Error.


Trap 7

Checked exceptions do not cause rollback by default.


Trap 8

Use rollbackFor for checked exceptions if needed.


Trap 9

Catching and swallowing exceptions can cause commit.


Trap 10

Dirty checking updates managed entities without explicit save().


Trap 11

New entities need save() or persist.


Trap 12

readOnly = true is mainly a hint and should not be used for write methods.


Trap 13

Flush is not the same as commit.


Trap 14

Keep transactions short.


Trap 15

Avoid slow external calls inside transactions.


45. Real Exam Question: Transaction

Question:

What is a transaction?

Answer:

A transaction is a unit of work that groups database operations so they either all succeed together or all roll back together.


46. Real Exam Question: @Transactional

Question:

What does @Transactional do?

Answer:

@Transactional tells Spring to run a method inside a transaction. Spring starts a transaction before the method, commits it if the method succeeds, and rolls it back if a rollback-triggering exception occurs.


47. Real Exam Question: Where to Put It

Question:

Where should @Transactional usually be placed?

Answer:

Usually on service layer methods, because services define business use cases and transaction boundaries.


48. Real Exam Question: Default Rollback

Question:

Which exceptions trigger rollback by default?

Answer:

By default, RuntimeException and Error trigger rollback. Checked exceptions do not trigger rollback by default.


49. Real Exam Question: rollbackFor

Question:

How can I make a checked exception trigger rollback?

Answer:

Use rollbackFor.

@Transactional(rollbackFor = IOException.class)

50. Real Exam Question: Persistence Context

Question:

What is the persistence context?

Answer:

The persistence context is a first-level cache and tracking area for managed JPA entities. It tracks entity changes during a transaction.


51. Real Exam Question: Dirty Checking

Question:

What is dirty checking?

Answer:

Dirty checking is the JPA mechanism that automatically detects changes to managed entities and synchronizes those changes to the database during flush or commit.


52. Real Exam Question: readOnly

Question:

What does @Transactional(readOnly = true) mean?

Answer:

It means the transaction is intended for read-only work. It can be used as an optimization hint and communicates intent, but it should not be used for methods that modify data.


53. Real Exam Question: Self-Invocation

Question:

Why can self-invocation break @Transactional?

Answer:

Because @Transactional is usually applied by a Spring proxy. If a method inside the same class calls another method in the same class, the call does not go through the proxy, so the transactional advice may not apply.


54. Real Exam Question: Flush vs Commit

Question:

What is the difference between flush and commit?

Answer:

Flush synchronizes persistence context changes with the database by sending SQL. Commit makes the transaction changes permanent.


55. Interview Answer

Question:

Explain @Transactional.

Good answer:

@Transactional defines a transaction boundary around a method. Spring uses AOP proxies to start a transaction before the method, commit it when the method completes successfully, and roll it back when a rollback-triggering exception occurs. It is usually placed on service methods because a service method represents a business unit of work.


56. Interview Answer

Question:

What is dirty checking?

Good answer:

Dirty checking is a JPA feature where changes to managed entities are automatically detected. If an entity is loaded inside a transaction and I change one of its fields, JPA tracks that change in the persistence context and sends an update SQL during flush or commit. That means I often do not need to call save() after modifying an existing managed entity.


57. Interview Answer

Question:

What causes rollback in Spring transactions?

Good answer:

By default, Spring rolls back transactions for unchecked exceptions, meaning RuntimeException, and also for Error. Checked exceptions do not cause rollback by default. If I need rollback for a checked exception, I can configure rollbackFor, for example @Transactional(rollbackFor = IOException.class).


58. Interview Answer

Question:

Why should transactions usually be in the service layer?

Good answer:

The service layer represents business use cases. A single service method may call multiple repositories, update several entities, and write audit logs. These operations should usually commit or roll back together. Controllers should focus on HTTP, and repositories should focus on persistence access, so the service layer is the best place for transaction boundaries.


59. Interview Answer

Question:

What is the self-invocation problem with @Transactional?

Good answer:

Spring usually applies @Transactional through a proxy. If a method in the same class calls another transactional method directly, the call does not go through the proxy. Because of that, the transactional advice may not run. A common fix is to put @Transactional on the outer public service method or move the transactional method to another Spring bean.


60. Tiny Code Practice

Service:

@Service
public class TaskService {

private final TaskRepository taskRepository;

public TaskService(TaskRepository taskRepository) {
this.taskRepository = taskRepository;
}

@Transactional
public TaskDto complete(Long id) {
TaskEntity task = taskRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Task", id));

task.complete();

return toDto(task);
}
}

Questions:

  1. Where does the transaction start?
  2. Is task managed?
  3. Why is no save() needed?
  4. When is the database updated?
  5. What happens if ResourceNotFoundException extends RuntimeException?

Answers:

  1. Before complete() runs.
  2. Yes, inside the transaction.
  3. Dirty checking tracks the managed entity change.
  4. During flush or commit.
  5. The transaction rolls back by default.

61. Tiny Bug Practice 1

Problem:

@Transactional(readOnly = true)
public void complete(Long id) {
TaskEntity task = taskRepository.findById(id).orElseThrow();
task.complete();
}

Question:

What is wrong?

Answer:

The method modifies data but is marked read-only. Use normal @Transactional for write methods.


62. Tiny Bug Practice 2

Problem:

@Transactional
public void importTasks() throws IOException {
taskRepository.save(new TaskEntity("A", "OPEN"));
throw new IOException("File error");
}

Question:

What is the trap?

Answer:

IOException is a checked exception. Checked exceptions do not trigger rollback by default. Use:

@Transactional(rollbackFor = IOException.class)

if rollback is required.


63. Tiny Bug Practice 3

Problem:

public void outer() {
inner();
}

@Transactional
public void inner() {
taskRepository.save(new TaskEntity("A", "OPEN"));
}

Question:

What is wrong?

Answer:

If outer() and inner() are in the same class, the internal call does not go through the Spring proxy. The @Transactional on inner() may not apply.


Practice Questions and Answers

Question 1

What is a transaction?

Answer:

A transaction is a unit of work that groups database operations so they either all succeed or all roll back together.


Question 2

Why do we need transactions?

Answer:

Transactions prevent half-finished database changes and protect data consistency.


Question 3

What does ACID mean?

Answer:

ACID means Atomicity, Consistency, Isolation, and Durability.


Question 4

What does @Transactional do?

Answer:

@Transactional tells Spring to run a method inside a transaction, committing on success and rolling back on rollback-triggering exceptions.


Question 5

Where should @Transactional usually be placed?

Answer:

Usually on public service methods, because services define business use cases and transaction boundaries.


Question 6

Why does @Transactional work through proxies?

Answer:

Spring uses AOP proxies to apply transaction behavior around method calls.


Question 7

What is the self-invocation trap?

Answer:

Self-invocation happens when one method in a class calls another method in the same class. The call bypasses the Spring proxy, so @Transactional may not apply.


Question 8

What is the persistence context?

Answer:

The persistence context is a first-level cache and tracking area for managed JPA entities.


Question 9

What is a managed entity?

Answer:

A managed entity is an entity currently tracked by the persistence context.


Question 10

What is dirty checking?

Answer:

Dirty checking is JPA’s automatic detection of changes to managed entities. Changes are synchronized to the database during flush or commit.


Question 11

Do I need save() after changing a managed entity?

Answer:

Usually no, not for an existing managed entity inside a transaction. Dirty checking can save the changes automatically.


Question 12

What is flush?

Answer:

Flush synchronizes persistence context changes with the database by sending SQL statements.


Question 13

What is the difference between flush and commit?

Answer:

Flush sends SQL to the database. Commit makes the transaction changes permanent.


Question 14

Which exceptions trigger rollback by default?

Answer:

RuntimeException and Error trigger rollback by default.


Question 15

Do checked exceptions trigger rollback by default?

Answer:

No. Checked exceptions do not trigger rollback by default.


Question 16

How can I roll back for a checked exception?

Answer:

Use rollbackFor.

@Transactional(rollbackFor = IOException.class)

Question 17

What happens if I catch and swallow a runtime exception inside a transactional method?

Answer:

If the exception is caught and not rethrown, the method may return normally and Spring may commit the transaction.


Question 18

What does readOnly = true mean?

Answer:

It means the transaction is intended for read-only work. It can be an optimization hint and communicates intent, but should not be used for writes.


Question 19

What is default transaction propagation?

Answer:

The default propagation is REQUIRED.


Question 20

Why should transactions be kept short?

Answer:

Long transactions can hold database resources and locks, reduce performance, and cause problems if they include slow external calls.

Final Memory Sentences

  • A transaction is an all-or-nothing unit of work.
  • @Transactional defines a transaction boundary.
  • Put transactions around business use cases, usually in services.
  • Spring applies @Transactional through AOP proxies.
  • Self-invocation can bypass @Transactional.
  • Default propagation is REQUIRED.
  • Persistence context tracks managed entities.
  • Dirty checking updates managed entities automatically.
  • New entities need save().
  • Existing managed entities can be updated without save().
  • Flush sends SQL.
  • Commit makes changes permanent.
  • Runtime exceptions roll back by default.
  • Checked exceptions do not roll back by default.
  • Use rollbackFor for checked exceptions.
  • Catching and swallowing exceptions can cause commit.
  • readOnly = true is for read methods.
  • Do not write data in read-only transactions.
  • Keep transactions short.
  • Avoid slow external calls inside transactions.