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:
- Declarative transaction management. Configured with annotations like
@Transactionaland used in 99% of cases (minimal code, maximum Spring magic). - 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:
- Before the method call: Spring opens a transaction.
- Method call: your business code runs.
- 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!
GO TO FULL VERSION