Spring Transaction Management
Spring Transaction Management
@Transactional In-Depth
Introduction
How plain JDBC Transaction Management works
How Spring’s or Spring Boot’s Transaction Management
works
How Spring and JPA / Hibernate Transaction Management
works
Fin
Acknowledgements
Introduction
In this guide you are going to learn about the main pillars
of Spring core’s transaction abstraction framework (a
confusing term, isn’t it?) - described with a lot of code
examples:
Why?
In the end, they all do the very same thing to open and close
(let’s call that 'manage') database transactions. Plain JDBC
transaction management code looks like this:
import java.sql.Connection;
try (connection) {
connection.setAutoCommit(false); // (2)
connection.commit(); // (3)
} catch (SQLException e) {
connection.rollback(); // (4)
isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED)
import java.sql.Connection;
// isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED
connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); // (1)
// propagation=TransactionDefinition.NESTED
...
connection.rollback(savePoint);
Here’s the catch: Whereas with plain JDBC you only have one
way (setAutocommit(false)) to manage transactions, Spring
offers you many different, more convenient ways to achieve
the same.
@Service
@Autowired
});
Back in the day, when XML configuration was the norm for
Spring projects, you could configure transactions directly in
XML. Apart from a couple of legacy, enterprise projects, you
won’t find this approach anymore in the wild, as it has been
superseded with the much simpler @Transactional annotation.
<tx:attributes>
<!-- other methods use the default transaction settings (see below) -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<aop:config>
</aop:config>
return id;
}
}
@Transactional
// userDao.save(user);
return id;
@Configuration
@EnableTransactionManagement
@Bean
try (connection) {
connection.setAutoCommit(false); // (1)
// userDao.save(user); <(2)
connection.commit(); // (1)
} catch (SQLException e) {
connection.rollback(); // (1)
Spring cannot really rewrite your Java class, like I did above,
to insert the connection code (unless you are using advanced
techniques like bytecode weaving, but we are ignoring that for
now).
As you can see from that diagram, the proxy has one job.
Quick Exam
@Configuration
@EnableTransactionManagement
public static class MyAppConfig {
@Bean
@Bean
@Bean
@Override
// ...
con.setAutoCommit(false);
@Override
// ...
Connection connection =
status.getTransaction().getConnectionHolder().getConnection();
try {
con.commit();
@Service
@Transactional
invoiceService.createPdf();
@Service
@Transactional
// ...
@Service
@Transactional(propagation = Propagation.REQUIRES_NEW)
// ...
@Transactional(propagation = Propagation.REQUIRED)
// or
@Transactional(propagation = Propagation.REQUIRES_NEW)
// etc
REQUIRED
SUPPORTS
MANDATORY
REQUIRES_NEW
NOT_SUPPORTED
NEVER
NESTED
Exercise:
Answers:
Required (default): My method needs a transaction, either
open one for me or use an existing one → getConnection().
setAutocommit(false). commit().
Supports: I don’t really care if a transaction is open or not,
i can work either way → nothing to do with JDBC
Mandatory: I’m not going to open up a transaction myself,
but I’m going to cry if no one else opened one up → nothing
to do with JDBC
Require_new: I want my completely own transaction
→ getConnection(). setAutocommit(false). commit().
Not_Supported: I really don’t like transactions, I will even
try and suspend a current, running transaction → nothing to
do with JDBC
Never: I’m going to cry if someone else started up a
transaction → nothing to do with JDBC
Nested: It sounds so complicated, but we are just talking
savepoints! → connection.setSavepoint()
@Transactional(propagation = Propagation.MANDATORY)
}
In this case, Spring will expect a transaction to be open,
whenever you call myMethod() of the UserService class.
It does not open one itself, instead, if you call that method
without a pre-existing transaction, Spring will throw an
exception. Keep this in mind as additional points for "logical
transaction handling".
@Transactional(isolation = Isolation.REPEATABLE_READ)
connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
There is one pitfall that Spring beginners usually run into. Have
a look at the following code:
@Service
@Transactional
@Transactional(propagation = Propagation.REQUIRES_NEW)
// ...
So far, we have only talked about plain, core Spring. But what
about Spring Boot? Or Spring Web MVC? Do they handle
transactions any differently?
@Autowired
session.beginTransaction();
session.save(user);
// and commit it
session.getTransaction().commit();
session.close();
}
}
In plain code:
@Service
@Autowired
@Transactional
sessionFactory.getCurrentSession().save(user); // (2)
Fin
By now, you should have a pretty good overview of how
transaction management works with the Spring framework and
how it also applies to other Spring libraries like Spring Boot or
Spring WebMVC. The biggest takeaway should be, that it does
not matter which framework you are using in the end, it is all
about the JDBC basics.