CodeGym /Courses /Module 5. Spring /Introduction to Transaction Management in Spring

Introduction to Transaction Management in Spring

Module 5. Spring
Level 6 , Lesson 1
Available

Spring Framework takes care of all the transaction hassles in your app. You just need to mark a method with @Transactional, and Spring will automatically wrap its execution in a transaction. No explicit begin, commit or rollback — the framework decides when to start a transaction and how to finish it.

Types of transaction management

In Spring there are two ways to manage transactions:

  1. Declarative transaction management. Configured with annotations like @Transactional and used in 99% of cases (minimal code, maximum Spring magic).
  2. Programmatic transaction management. When you need finer control, you can use special classes like TransactionTemplate.

We'll focus on the declarative approach to transaction management, though we'll touch on the programmatic way too. With the declarative approach most tasks are solved with a single annotation — no manual transaction handling, everything happens automatically.

Transaction management architecture in Spring

Behind the scenes Spring uses a pretty sophisticated mechanism for transactions. How does Spring know where to start a transaction? When to finish it? And who even watches the whole process? Let's break down how it all works.

TransactionManager: the main conductor of transactions

Spring uses the concept of a TransactionManager to manage transactions. This is the central component that coordinates beginning, committing, and rolling back transactions.

Here are the main implementations you'll commonly see:

  • DataSourceTransactionManager — works with JDBC (good old SQL).
  • JpaTransactionManager — for those using JPA/Hibernate. We'll work with it in many examples.
  • JtaTransactionManager — suitable for distributed transactions via JTA (Java Transaction API).

Spring automatically picks the appropriate TransactionManager based on your dependencies. So if you're using JPA, don't be surprised when you see Spring "guessing" everything. Folks, it's not guessing — it's Spring auto-configuration!


Support for different technologies

It's important to note that Spring can manage transactions across many platforms and technologies, including:

  • JDBC — for working directly with databases.
  • JPA/Hibernate — for ORM solutions.
  • JTA — for working in distributed systems (for example, microservices).

Want more examples? Then let's create a TransactionManager in a real app.


Example: configuring a TransactionManager

To work with transactions we need to add a transaction manager to our app. Configuration in Spring Boot is insanely simple. Let's go through an example for JPA:


@Configuration
@EnableTransactionManagement // Enable transaction management
public class AppConfig {

    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
        return new JpaTransactionManager(emf); // Manager for JPA
    }
}

That's it! Now Spring knows which manager to use for transaction management.


A bit about declarative vs programmatic management

Let's quickly go over the two transaction management strategies Spring offers.

Declarative transaction management

With the declarative approach you just annotate a method or class with @Transactional. Spring takes care of starting a transaction before the method runs and finishing it afterwards.

Example:


@Service
public class BankService {

    @Transactional
    public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
        // Money transfer logic
        accountRepository.debit(fromAccountId, amount);
        accountRepository.credit(toAccountId, amount);
    }
}

As you can see, no manual calls to begin() or commit() — just the annotation and clean business code.

Programmatic transaction management

Sometimes you need to manage transactions manually. For example, if you require complex branching logic where each branch might run in its own transaction.

Example:


@Service
public class ManualTransactionService {

    @Autowired
    private PlatformTransactionManager transactionManager;

    public void performAction() {
        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);

        transactionTemplate.execute(status -> {
            // Your code inside the transaction
            performSomeDBOperations();
            return null; // null means everything succeeded
        });
    }
}

Pretty neat, right? But let's be honest: the programmatic approach is more of an exception than the rule.


How transaction management works in Spring

Spring uses AOP (aspect-oriented programming) for transaction management. When you annotate a method with @Transactional, Spring actually creates a proxy object for your bean. That proxy intercepts method calls and wraps them in transactions.

Simply put:

  1. Before the method call: Spring opens a transaction.
  2. Method call: your business code runs.
  3. After the call: the transaction is either committed or rolled back (if an error occurred).

Example: transaction setup and usage

Let's walk through a full transaction setup in Spring Boot. Imagine a simple app for managing books in a library. We need to create a Book entity, a repository, a service layer, and a controller.

Entity Book


@Entity
public class Book {

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

    private String title;

    private String author;

    // Getters and setters...
}

Repository

We'll use JpaRepository to work with the database.


public interface BookRepository extends JpaRepository<Book, Long> {
    // No code needed here, JpaRepository magic does everything for us
}

Service layer with transactions


@Service
public class BookService {

    private final BookRepository bookRepository;

    @Autowired
    public BookService(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    @Transactional
    public Book saveBook(Book book) {
        return bookRepository.save(book);
    }

    @Transactional
    public void deleteBook(Long id) {
        bookRepository.deleteById(id);
    }
}

Here we're telling Spring: "Hey buddy, wrap all methods in a transaction!"

Controller


@RestController
@RequestMapping("/books")
public class BookController {

    private final BookService bookService;

    @Autowired
    public BookController(BookService bookService) {
        this.bookService = bookService;
    }

    @PostMapping
    public ResponseEntity<Book> createBook(@RequestBody Book book) {
        Book createdBook = bookService.saveBook(book);
        return ResponseEntity.ok(createdBook);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteBook(@PathVariable Long id) {
        bookService.deleteBook(id);
        return ResponseEntity.noContent().build();
    }
}

Now you can create and delete books in your library, and it's all under transactions.


Important nuances: errors and gotchas

One common mistake is annotating private methods with @Transactional. Remember, transactions work via proxies, so Spring only sees calls to public methods.

Also remember that if a method calls another method of the same class, the @Transactional on the second method might be ignored.


You can see how simple and elegant Spring handles transactions, giving us a powerful tool for data management and consistency. In the following lectures we'll dive deeper into the @Transactional annotation and its settings. Get ready for new discoveries!

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION