Write before translation

The original link is: the Spring Transaction Management: @ Transactional – the Depth In | MarcoBehler

The executable project code is not configured. To help with understanding and debugging, it is recommended to download the POC/Spring-Transaction sample project and learn as you go. The example project uses the Embedded H2 database, initializes the data structure through Spring Schema.sql, and performs basic CURD queries by accessing the http://localhost:8080/h2-console management interface. It is convenient for readers to verify data. Of course, experienced readers can also switch to mysql as the data source, after all, its GUI tools are more complete.

Why should I translate this article? There are three reasons:

  • The explanation is thorough, the author’s professional foundation is solid, reading straight call color.
  • The content is simple and profound, from the principle to the upper practice, the author deconstructs a feature from the bottom up thinking worth learning.
  • Arranged as a heuristic architecture, the whole process from the question leads to the solution, from the solution leads to the next question, interlocking.

⬇️⬇️⬇️ please refer to ⬇️ port

You can use this article to develop a concise and practical understanding of how the @Transactional annotation works in Spring transaction management.

The only prerequisite for reading? You need a general understanding of the DATABASE ACID principle, what database transactions are and why we need them. In addition, distributed and Reactive transactions are not covered in this article, although some of the general principles mentioned below apply in Spring.

Introduction to the

In this article, you will learn the core concepts of Spring’s transaction Abstraction framework (Blackface + question mark?). There is also plenty of sample code to help you understand

  • @TransactionalDeclarative transaction management vs programmatic transaction management
  • Physical vs. logical transactions
  • Spring @TransactionalIntegration with JPA/Hibernate
  • Spring @TransactionalIntegrate with Spring Boot or Spring MVC
  • Rollback, proxy, common traps, etc

This article won’t let you get lost in the upper concepts of Spring compared to the official Spring documentation. Instead, you’ll learn about Spring transaction management in an unusual way. Start at the bottom and work your way up. That is, you will start with plain, raw JDBC transactions.

How do normal JDBC transactions work

If you don’t have a thorough understanding of JDBC transactions, don’t think about skipping this chapter.

How do I start, commit, or rollback JDBC transactions

The first important point is this: whether you are using Spring @Transactional, Hibernate, JOOQ, or any other database class.

Eventually, they all do the same thing to turn on and off (or “manage”) database transactions. The pure JDBC transaction management code is as follows:

import java.sql.Connection;

Connection connection = dataSource.getConnection(); / / (1)

try (connection) {
    connection.setAutoCommit(false); / / (2)
    // execute some SQL statements...
    connection.commit(); / / (3)

} catch (SQLException e) {
    connection.rollback(); / / (4)
}
Copy the code
  1. You need to establish a database link to start the transaction. Although in most enterprise applications you will get connected by data source configuration, but separate DriverManager. GetConnection (url, user, password) can also work well.
  2. This is aThe onlyEven if the name doesn’t sound right, start a database transaction in JAVA.setAutoCommit(true)Wraps all SQL expressions within its transaction, whilesetAutoCommit(false)On the contrary: you can turn transactions on and off based on it.
  3. Commit execution transaction…
  4. Or, if something unexpected happens, roll back our changes.

These four lines of highly simplified code are all that Spring @Transactional does behind the scenes for you. In the next chapter, you will learn how they work. Before we get to that, we have a little bit more to add.

Quick start: Connection pools like HikariCP can automatically switch to AutoCOMMIT mode depending on configuration. But this is an advanced topic, no further.)

How to use JDBC Isolation levels and Savepoints

If you’ve used the Spring @Transactional annotation, you’ve probably come across something like this:

@Transactional(propagation=TransactionDefinition.NESTED, isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED)
Copy the code

We’ll cover Spring’s nested transactions and isolation levels in more detail later, but repeat them here because these parameters can ultimately be distilled into JDBC code as follows:

import java.sql.Connection;

// isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED

connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); / / (1)

// propagation=TransactionDefinition.NESTED

Savepoint savePoint = connection.setSavepoint(); / / (2). connection.rollback(savePoint);Copy the code
  1. This shows how Spring sets isolation levels on database connections. Isn’t it anything like Rocket Science?
  2. Nested transactions in Spring are equivalent to savepoints in JDBC. If you don’t know what a savepoint is, you can look it up, rightThis tutorialTranslator’s note: See ancillary projectsPlainOldJDBCSampleImplementation “. Note that savepoint support depends on your JDBC driver/database.

How do Spring or Spring Boot transactions work

Now that you have a basic understanding of JDBC transactions, let’s explore pure Spring core transactions. Everything here works 1:1 for Spring Boot and Sring MVC, but with a few additions.

What exactly is the Spring Transaction Management or Transaction Abstraction framework (more confusingly named)?

Remember, transaction management can be simply understood as: How does Spring start, commit, or rollback JDBC transactions? Does that sound similar?

Get to the point: Based on JDBC you only have one method (setAutocommit(false)) to turn on transaction management, and Spring provides many different, but more convenient, encapsulation to do the same thing.

How do I use Spring programmatic transaction Management?

At first, but now rarely used, is the Spring through programming defined transaction: through the PlatformTransactionManager TransactionTemplate or directly use. See the BookingServcie implementation for an example of this code:

@Service
public class UserService {

    @Autowired
    private TransactionTemplate template;

    public Long registerUser(User user) {
        Long id = template.execute(status ->  {
            // execute some SQL that e.g.
            // inserts the user into the db and returns the autogenerated id
            returnid; }); }}Copy the code

Compare with the JDBC example:

  • You no longer need to manually open and close database connections (try-finally), but Transaction Callbacks instead.
  • You also no longer need to manually captureSQLExceptionsSpring converts these exceptions into runtime exceptions.
  • Also, your code will be better integrated into the Spring ecosystem.TransactionTemplateIt’s going to be used internallyTransactionManager, which uses a data source. These are all things you need to be in advanceSpring contextYou don’t need to worry about Beans defined in the configuration.

While this is no small improvement, programmatic transaction management is not the primary focus of the Spring transaction framework. Instead, declarative transaction management is the big deal. Let’s find out

How do I use Spring’s XML declarative transaction management?

In the past, using XML for configuration was standard in Spring projects, and you could configure transactions directly in XML files. Today, however, with the exception of a few historical and enterprise projects, you won’t find this usage in everyday development, replaced by the more concise @Transactional annotation.

We won’t go into XML configuration in depth in this article, but if you’re interested, you can use this example as a starting point for further research (the example is taken directly from Official Spring Documentation)

<! -- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) -->
<tx:advice id="txAdvice" transaction-manager="txManager">
  <! -- the transactional semantics... -->
  <tx:attributes>
    <! -- all methods starting with 'get' are read-only -->
    <tx:method name="get*" read-only="true"/>
    <! -- other methods use the default transaction settings (see below) -->
    <tx:method name="*"/>
  </tx:attributes>
</tx:advice>
Copy the code

You defined AOP advice (aspect oriented programming) through the XML above, which you can apply to the UserService Bean through the following configuration.

<aop:config>
  <aop:pointcut id="userServiceOperation" expression="execution(* x.y.service.UserService.*(..) )"/>
  <aop:advisor advice-ref="txAdvice" pointcut-ref="userServiceOperation"/>
</aop:config>

<bean id="userService" class="x.y.service.UserService"/>
Copy the code

Your UserService bean looks like this:

public class UserService {

    public Long registerUser(User user) {
        // execute some SQL that e.g.
        // inserts the user into the db and retrieves the autogenerated id
        returnid; }}Copy the code

From a Java code point of view, this declarative transaction implementation is much simpler than programming. However, a lot of complex and redundant XML is derived to configure the Pointcut and Advisor.

How to use Spring@TransactionalAnnotations (declarative transaction management)

Let’s take a look at how Spring transaction management is commonly used today:

public class UserService {

    @Transactional
    public Long registerUser(User user) {
        // execute some SQL that e.g.
        // inserts the user into the db and retrieves the autogenerated id
        // userDao.save(user);
        returnid; }}Copy the code

How is this done? No redundant XML configuration and extra encoding. Instead, you just need to do two things:

  • Make sure your Spring configuration is added@EnableTransactionManagementAnnotation (Spring Boot will automatically enable it for you)
  • Make sure you specify a transaction manager in your Spring configuration (this will need to be done yourself)spring-boot-starter-data-jdbcCan automatically configure the transaction manager for you.”
  • After that, Spring is smart enough to handle transactions for you, and it’s all transparent to you: any beans are labeled@TransactionalIs executed in a database transaction (note: this is available hereSome of the pit).

So, for @Transactional to work, you need:

@Configuration
@EnableTransactionManagement
public class MySpringConfig {

    @Bean
    public PlatformTransactionManager txManager(a) {
        return yourTxManager; // more on that later}}Copy the code

Now, what do I mean when I say That Spring handles transactions transparently for you? With your knowledge of the JDBC transaction example, the @Transactional annotation UserService can be translated simply as:

public class UserService {

    public Long registerUser(User user) {
        Connection connection = dataSource.getConnection(); / / (1)
        try (connection) {
            connection.setAutoCommit(false); / / (1)

            // execute some SQL that e.g.
            // inserts the user into the db and retrieves the autogenerated id
            // userDao.save(user); < (2)

            connection.commit(); / / (1)
        } catch (SQLException e) {
            connection.rollback(); / / (1)}}}Copy the code
  1. These are standard database connection open and close operations. Spring transaction management does all this for you automatically, without you having to explicitly code it.
  2. This is your business code, saving users via a DAO, etc

This example looks like magic, so let’s explore how Spring automatically inserts this connection code for you.

CGLIB & JDK proxy – in@Transactionalunder

Spring can’t really rewrite your Java classes to insert connection code like I did above (unless you use advanced techniques like bytecode enhancement, which we’ll ignore here).

Your registerUser() method is still just calling userdao.save (user), which can’t be changed in real time.

But Spring has its advantages. At the core layer, it has an IoC container. It instantiates a UserService singleton and can be automatically injected into any Bean that requires UserService.

Whenever you use @Transactional ona Bean, Spring uses a trick. It does not directly instantiate a UserService primitive object, rather than a Transaction proxy object of UserService.

With the power of the Cglib Library, it can be implemented using proxy-through-subclassing. Proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies proxies

Let’s look at a diagram to see how the agent works:As you can see in the picture, this agent does one thing:

  • Open and close database connections and transactions
  • Then the proxy goes to the original that you wroteUserServiceobject
  • And finally other Beans, like yoursUserRestControllerNever know that they are interacting with a proxy object, not the original object.

Quick Exam take a look at the source code below and tell me what type of UserService Spring will automatically construct, assuming it is marked as @Transactional or has an @Transactional method.

@Configuration
@EnableTransactionManagement
public static class MyAppConfig {

    @Bean
    public UserService userService(a) {  / / (1)
        return newUserService(); }}Copy the code
  1. Right answer: Spring constructs one hereUserServiceClass dynamic CGLib proxy that can open and close database transactions for you. You or any other Bean will never notice that this is not primitiveUserServiceObject, but oneUserServiceProxy object of.

You need to what kind of transaction manager (for example: the PlatformTransactionManager)?

There is only one important point left, although we have mentioned it many times before.

Your UserService can dynamically generate proxy classes, and proxies can manage transactions for you. However, it is not the proxy class itself that handles the transaction state (open, COMMIT, close), but rather delegates it to the Transaction Manager.

Spring provides PlatformTransactionManager/TransactionManager interface definition, the default also provides some practical implementation. One of them is the Datasource Transaction Manager.

It’s exactly the same as you’ve done so far to manage transactions, but first let’s look at the Spring configuration required:

// Spring Boot also has automatic configuration.
// org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
@Bean
public DataSource dataSource(a) {
    return new MysqlDataSource(); / / (1)
}

// Spring Boot also has automatic configuration.
// org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration
@Bean
public PlatformTransactionManager txManager(a) {
    return new DataSourceTransactionManager(dataSource()); / / (2)
}
Copy the code
  1. Here you create a data source that specifies a database or connection pool. Mysql used in the example.
  2. Here you create a transaction manager that takes a data source as an input parameter to manage the transaction.

A brief introduction. All transaction managers have methods like “doBegin” (for starting a transaction) or “doCommit”, similar to the following simplified code directly extracted from Spring source code:

public class DataSourceTransactionManager implements PlatformTransactionManager {

    @Override
    protected void doBegin(Object transaction, TransactionDefinition definition) {
        Connection newCon = obtainDataSource().getConnection();
        // ...
        con.setAutoCommit(false);
        // yes, that's it!
    }

    @Override
    protected void doCommit(DefaultTransactionStatus status) {
        // ...
        Connection connection = status.getTransaction().getConnectionHolder().getConnection();
        try {
            con.commit();
        } catch (SQLException ex) {
            throw new TransactionSystemException("Could not commit JDBC transaction", ex); }}}Copy the code

Therefore, the data source transaction manager will use almost exactly the same code as JDBC when managing transactions.

With this in mind, we expand our flow chart based on the above conclusion:The summary is as follows:

  1. If Spring detects it on the Bean@TransactionalAnnotation, which creates a dynamic proxy class for the Bean.
  2. The proxy class can access the transaction manager and ask it to open and close the transaction/connection.
  3. The transaction manager itself simply does what you used to do manually: manage a practical, traditional JDBC connection.

What is the difference between physical and logical transactions?

Imagine the following two transaction classes.

@Service
public class UserService {

    @Autowired
    private InvoiceService invoiceService;

    @Transactional
    public void invoice(a) {
        invoiceService.createPdf();
        // send invoice as email, etc.}}@Service
public class InvoiceService {

    @Transactional
    public void createPdf(a) {
        // ...}}Copy the code

UserService has an invoice() transaction method. It invokes the createPdf() transaction method on another InvoiceService class.

Now in terms of database transactions, there is only one database transaction. (Remember: _getConnection(), setAutocommit(false), commit() _). Spring calls it a physical transaction, which can be confusing.

From Spring’s point of view, however, there are two logical transactions: the first in UserService and the other in InvoiceService. Spring is smart enough to know that two methods with the @Transactional tag use the same physical database transaction underneath.

How would the rendering be different if we made the following changes?

@Service
public class InvoiceService {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void createPdf(a) {
        // ...}}Copy the code

Changing the transaction propagation mode to REQUIRES_new tells Spring that createPDF() needs to execute in its own transaction, separate from any other transactions that are already in place. Thinking back to the original Java version before this article, did you see a way to split a transaction in two? I didn’t see it.

This means that your underlying code opens 2 (physical) connections/transactions to the database (again: getConnection() x2, setAutocommit(false) x2, Commit () x2). Spring can still smartly map two logical transactions (invoice()/createPdf()) to two different physical database transactions.

Therefore, the summary is as follows:

  • Physical transactions: Actually equivalent to JDBC transactions
  • Logical transactions: by Spring@TransactionalMethod of marking (possible nesting)

Next, we’ll take a closer look at the details of the transaction propagation pattern.

@Transactional[Propagation Levels]

When you look at the Spring source code, you will see that there are various propagation levels that can be mounted on the @Transactional method.

This is the default propagation mode in Spring.
@Transactional(propagation = Propagation.REQUIRED)

// or

@Transactional(propagation = Propagation.REQUIRES_NEW)
// etc
Copy the code

The full list is as follows:

  • REQUIRED
  • SUPPORTS
  • MANDATORY
  • REQUIRES_NEW
  • NOT_SUPPORTED
  • NEVER
  • NESTED

Exercise: In the raw Java implementation section, I showed you all the things JDBC can do to transactions. Take a few minutes to think about what each Spring propagation pattern does at the database or JDBC connection level.

And then look at the solution below.

Answers:

  • Required (default) :My method requires transaction support, use an existing one or create a new one for me →GetConnection (), setAutocommit(false), commit().
  • Supports: I don’t care if there are transactions open, I can work normally → I don’t do anything at the JDBC level
  • Mandatory: I do not intend to open a transaction for myself, but if no one does it for me, I cry “I need a transaction, but I need someone else to open it for me, or I will throw a mistake” → do nothing at the JDBC level
  • Require_new:I need an exclusive transaction →GetConnection (), setAutocommit(false), commit().
  • Not_Supported: I don’t like transactions and I will even suspend the current running transaction → not do anything at the JDBC level
  • Never: If someone starts a transaction for me, I scream and scream → don’t do anything at the JDBC level
  • Nested:It sounds complicated, but we’re talking about save points! –connection.setSavepoint()

As you can see, most propagation patterns do nothing at the database or JDBC level. It’s more about organizing your code with Spring and telling it how/when/where transactions are needed.

Take a look at this example:

public class UserService {

     @Transactional(propagation = Propagation.MANDATORY)
     public void myMethod(a) {
        // execute some sql}}Copy the code

In the example, anytime you call the myMethod() method of UserService, Spring expects an open transaction. It does not open for itself; instead, Spring throws an exception when a method is called without an already opened transaction. Remember this “logical transaction processing” supplement.

@TransactionalWhat do Isolation Levels stand for?

It’s a clever question, but what happens when you configure:

@Transactional(isolation = Isolation.REPEATABLE_READ)
Copy the code

Ha, this can simply be equivalent to:

connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
Copy the code

Database transaction isolation levels, however, are a complex subject that will take some time to master on your own. The Isolation Levels section in the official Pstgres documentation is a good place to start.

Again, when you switch isolation levels in a transaction, you must confirm in advance whether the underlying JDBC driver/database supports the features you need.

The easiest to step on@TransactionalThe pit of

Here is a common pitfall for Spring beginners. Take a look at this code:

@Service
public class UserService {

    @Transactional
    public void invoice(a) {
        createPdf();
        // send invoice as email, etc.
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void createPdf(a) {
        // ...}}Copy the code

You have a UserService class, and the transaction method invoice calls the transaction method createPdf() inside.

So, when someone calls invoice(), how many physical transactions end up being opened?

The answer is not 2, but 1. Why?

Let’s go back to the proxy chapter of this article. Spring creates the UserService proxy class for you, but method calls inside the proxy class cannot be propped. That is, no new transactions are generated for you.

Take a look at an example:Here are some tips (e.g.self-injectionTranslator’s note: See sample projectInnerCallSercie“) can help you get around that limitation. But the main takeaway: always keep the boundaries of agency transactions in mind.

How to use it in Spring Boot or Spring MVC@Transactional

We’ve only discussed pure core Spring usage so far. Will there be any usage differences in Spring Boot or Spring MVC?

The answer is: no.

No matter use what kind of frame (or more precisely: Spring all framework of the ecological system), you will always use @ Transactional annotation, cooperate with the transaction manager, as well as @ EnableTransactionManagement annotation. There is no other use.

With Spring, however, the only difference is that the Boot automatically by the JDBC configuration, it will automatically set @ EnableTransactionManagement annotations, and create the PlatformTransactionManager for you.

How Spring handles rollback (and the default rollback strategy)

Spring rollback will be covered in the next article revision.

Translator’s Note: Rollback in Spring Boot is implemented using the @Transactional annotation on the ROLLBACK family configuration. You can check the source annotations to see how this is done. The annotations are complete, and essentially determine when to call commit based on the configuration conditions. When to call ROLLBACK”

How do Spring and JPA/Hibernate transaction management work together

Goal: Synchronize Spring@TransactionalAnd Hibernate/JPA

At this point, you expect Spring to integrate with other database frameworks, such as Hibernate (a popular JPA implementation) or Jooq.

Let me look at a pure Hibernate example (note: It doesn’t matter if you use Hibernate directly or through JPA).

Use Hibernate to rewrite UserService as follows:

public class UserService {

    @Autowired
    private SessionFactory sessionFactory; / / (1)

    public void registerUser(User user) {

        Session session = sessionFactory.openSession(); / / (2)

        // lets open up a transaction. remember setAutocommit(false)!
        session.beginTransaction();

        // save == insert our objects
        session.save(user);

        // and commit it
        session.getTransaction().commit();

        // close the session == our jdbc connectionsession.close(); }}Copy the code
  1. This is the pure, raw Hibernate SessionFactory, the entry point for all Hibernate queries
  2. Manually manage sessions (read: database connections) and transactions via Hibernate’s API

However, there is one big problem with this code:

  • Hibernate does not recognize Spring@Transactionalannotations
  • Spring @TransactionalI also don’t know Hibernate’s transaction encapsulation concept

But ultimately Spring and Hibernate can be seamlessly integrated, meaning they can actually understand the transaction concept of objects.

The code is as follows:

@Service
public class UserService {

    @Autowired
    private SessionFactory sessionFactory; / / (1)

    @Transactional
    public void registerUser(User user) {
        sessionFactory.getCurrentSession().save(user); / / (2)}}Copy the code
  1. Same SessionFactory as above
  2. There is no need for manual state management. On the contrary,getCurrentSession()@TransactionalIt’s synchronous.

How is this done?

The use of HibernateTransactionManager

There is a very simple solution to this integration problem:

Use DataSourcePlatformTransactionManager than in the Spring configuration, You can replace HibernateTransactionManager (if you use the native Hibernate) or JpaTransactionManager (if by JPA using Hibernate)

This customized HibernateTransactionManager will ensure that:

  • Manage transactions through Hibernate (SessionFactory).
  • Smart enough to allow Spring to use the same transaction annotations in non-Hibernate, i.e@Transactional

As always, a picture is worth a thousand words (though note that the flow between the broker and the real service is highly abstract and simplified here).The appeal is a simple summary of the way SPring and Hibernate are integrated.

In understanding or before going to an in-depth understanding of other integration way, take a look at the Spring to provide all the PlatformTransactionManager implementation, will go a long way.

The last

By now, you should have a good idea of how the Spring framework handles transactions and how it applies to other Spring class libraries, such as Spring Boot or Spring WebMVC. The biggest takeaway should be that it doesn’t matter which framework you end up using; it all maps to the underlying concepts of JDBC.

Understanding them correctly (remember: getConnection(), setAutocommit(false), commit()) will make it easier to grasp the essence of complex enterprise projects in the future.

Thanks for reading.

thanks

Thanks to Andreas Eisele for his feedback on an earlier version of this guide. Thanks to Ben Horsfield for providing much-needed Javascirpt code to enhance the reading experience of this guide.