Copyright belongs to the author, any form of reprint please contact the author to obtain authorization and indicate the source.

The Spring transaction

Spring transaction mechanism mainly includes declarative transaction and programmatic transaction, declarative transaction lets us get rid of complex transaction processing, programmatic transaction is not widely used in the actual development, only for learning reference.

Transaction abstraction

Spring’s transaction management provides a unified API to support different resources and provides declarative transactions for easy integration with the Spring framework. The spring transaction manager is implemented using an abstract design approach. Here is the logic implementation code in the Spring transaction manager (simplified to highlight the core logic)

## Transaction status
public interface TransactionStatus extends SavepointManager{
    boolean isNewTransaction();
    boolean hasSavepoint();
    void setRollbackOnly();
    boolean isRollbackOnly();
    boolean isCompleted();
}
## Transaction definition
public interface TransactionDefinition{
    int getPropagationBehavior();  # Communication behavior
    int getIsolationLevel();   # Isolation level
    String getName();   # of transaction
    int getTimeout();    # timeout
    boolean isReadOnly();   Read only
}
## Transaction manager
public interface PlatformTransactionManager{
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
    void commit(TransactionStatus status) throws TransactionException;
    void rollback(TransactionStatus status) throws TransactionException;
}
Copy the code

Propagation behavior of transactions

Spring specifies seven types of transaction propagation behavior in the TransactionDefinition interface. They specify how transactions propagate when nested calls to transaction methods and transaction methods occur

Seven behaviors of transaction propagation:

Type of transaction propagation behavior instructions
PROPAGATION_REQUIRED If there is no transaction, create a new one. If there is already one, join it. This is the most common choice.
PROPAGATION_SUPPORTS Current transactions are supported, and non-transactionally executed if there are none.
PROPAGATION_MANDATORY Use the current transaction and throw an exception if there is no transaction currently.
PROPAGATION_REQUIRES_NEW Create a new transaction and suspend the current transaction if one exists.
PROPAGATION_NOT_SUPPORTED Performs the operation nontransactionally, suspending the current transaction if one exists.
PROPAGATION_NEVER Executes nontransactionally, throwing an exception if a transaction currently exists.
PROPAGATION_NESTED If a transaction currently exists, it is executed within a nested transaction. If there are no transactions currently, an operation similar to PROPAGATION_REQUIRED is performed.

When usingPROPAGATION_NESTED, the underlying data source must be based on JDBC 3.0, and the implementer needs to support savepoint transactions.

Transaction isolation level

If spring does not specify a transaction isolation level, spring’s transaction isolation level follows that of the database, and spring is the same isolation level as the database. Isolation level of Spring transactions:

  1. ISOLATION_DEFAULT (default) : this is a PlatfromTransactionManager the default isolation level, using the database to the default transaction isolation level. The other four correspond to JDBC isolation levels
  2. ISOLATION_READ_UNCOMMITTED: This is the lowest isolation level for a transaction, and it allows another transaction to see the uncommitted data of this transaction. This isolation level produces dirty reads, non-repeatable reads, and phantom reads.
  3. ISOLATION_READ_COMMITTED: Ensures that data modified by one transaction is committed before it can be read by another transaction. Another transaction cannot read uncommitted data from that transaction
  4. ISOLATION_REPEATABLE_READ(repeatable read) : This transaction isolation level prevents dirty, non-repeatable reads. But illusionary reading can occur. In addition to ensuring that one transaction cannot read uncommitted data from another transaction, it also ensures that the following situation (unrepeatable reads) is avoided.
  5. ISOLATION_SERIALIZABLE: This is the most expensive but reliable transaction isolation level. Transactions are processed for sequential execution. In addition to preventing dirty read, not repeat read, but also avoid phantom read.

Let’s take a look at code versus label transaction implementations:

## Code implementationpublic OrderService{ @Autowire PlatformTransactionManager txManager; void buyTicket(BuyTicketDTO dto){ DefaultTransactionDefinition def = new DefaultTransactionDefinition(); / / set the transaction propagation behavior def. SetPropagationBehavior (TransactionDefinition. PROPAGATION_REQUIRED); TransactionStatus status = txManager.getTransaction(def); try{Execute the business codetxManager.commit(status); }catch(Exception e){ txManager.rollback(status); }}}## Transaction tag implementationpublic OrderService{ @Transactonal void buyTick(BuyTicketDTO dto){ // get and begin transaction manager from context Try {/** transaction}catch(Exception e){// auto rollback Transaction}}Copy the code

PlatformTransactionManager



DataSourceTransactionManager
JpTransactionManager
JmsTransactionManager
JtaTransactionManager

Introduction to JPA and transaction implementation

JPA is a specification for Java (Java Persistence API). It is used to store data between Java objects and relational databases. JPA acts as a bridge between object-oriented domain models and relational database systems. Because JPA is just a specification, it does nothing on its own. It needs an implementation. Therefore, ORM tools such as Hibernate, TopLink, and iBatis implement the JPA data persistence specification.

Code for JPA transaction instances: Domian entity objects

@Entity(name = "customer")
@Data
public class Customer {
    ## id grows automatically
    @Id
    @GeneratedValue
    private Long id;
    ## Unique index
    @Column(name = "user_name", unique = true)
    private String username;
    private String password;
    private String role;
}
Copy the code

The dao interface

// Inherits the method in JpaRepository, This already includes the basic CRUD public Interface CustomerRepository extends JpaRepository<Customer, Long> { Customer findOneByUsername(String username); }Copy the code

The effect of the following two transaction implementations is the same. The purpose is to show you how to use code to achieve the same effect as a transaction declared by an annotation.

@Service
public class CustomerServiceTxInAnnotation {
    private static final Logger LOG = LoggerFactory.getLogger(CustomerServiceTxInAnnotation.class);
    
    @Autowired
    private CustomerRepository customerRepository;
    Use annotations to declare the transaction exists
    @Transactional
    public Customer create(Customer customer) {
        LOG.info("CustomerService In Annotation create customer:{}", customer.getUsername());
        if(customer.getId() ! = null) { throw new RuntimeException("User already exists");
        }
        customer.setUsername("Annotation:" + customer.getUsername());
        returncustomerRepository.save(customer); }}Copy the code
@Service
public class CustomerServiceTxInCode {
    private static final Logger LOG = LoggerFactory.getLogger(CustomerServiceTxInCode.class);

    @Autowired
    private CustomerRepository customerRepository;
    @Autowired
    private PlatformTransactionManager transactionManager;

    public Customer create(Customer customer) {
        LOG.info("CustomerService In Code create customer:{}", customer.getUsername());
        if(customer.getId() ! = null) { throw new RuntimeException("User already exists");
        }
        Use code to declare transaction existence
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
        When using the ISOLATION_SERIALIZABLE level, if no external transaction exists, the transaction itself is created, so the submitError method throws an exception and can be rolled back
        //def.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
        When you use the PROPAGATION_REQUIRED level, if no external transaction exists, there is no transaction itself, so the submitError method throws an exception and still saves data successfully
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        def.setTimeout(15);
        TransactionStatus status = transactionManager.getTransaction(def);
        try {
            customer.setUsername("Code:" + customer.getUsername());
            customerRepository.save(customer);
            submitError();
            transactionManager.commit(status);
            return customer;
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
    private void submitError(){
        throw new RuntimeException("some Data error ")}}Copy the code

The controller control layer

@RestController
@RequestMapping("/api/customer")
public class CustomerResource {
    private static final Logger LOG = LoggerFactory.getLogger(CustomerResource.class);

    @Autowired
    private CustomerServiceTxInAnnotation customerService;
    @Autowired
    private CustomerServiceTxInCode customerServiceInCode;
    @Autowired
    private CustomerRepository customerRepository;

    @PostMapping("/annotation")
    public Customer createInAnnotation(@RequestBody Customer customer) {
        LOG.info("CustomerResource create in annotation create customer:{}", customer.getUsername());
        return customerService.create(customer);
    }
    @PostMapping("/code")
    public Customer createInCode(@RequestBody Customer customer) {
        LOG.info("CustomerResource create in code create customer:{}", customer.getUsername());
        return customerServiceInCode.create(customer);
    }
    @GetMapping("")
    public List<Customer> getAll() {
        returncustomerRepository.findAll(); }}Copy the code

Let’s look at the execution results of the program and the management process of JPA transactions:

Spring transaction control is used throughout the transaction management process, and the ASSOCIATED ORM framework implements the JPA specification

JMS Transaction principles

Spring JMS Session

  • Transaction management operations are performed through the Session
  • Session is a thread-bound
  • Transaction context: One Session per thread

Two Spring JMS transaction types

  • Session-managed transactions: native transactions
  • Externally managed transactions: JmsTransactionManager, JTA

Srping JMS transaction mechanism procedure

Session native transaction:

JmsTransactionManager externally managed transactions:

## Annotate the Spring Bean
@EnableJms
@Configuration
public class JmsConfig {
    private static final Logger LOG = LoggerFactory.getLogger(CustomerService.class);

    @Bean
    public JmsTemplate initJmsTemplate(ConnectionFactory connectionFactory) {
        LOG.debug("init jms template with converter.");
        JmsTemplate template = new JmsTemplate();
        ## The connectionFactory used by JmsTemplate must be the same as the One used by JmsTransactionManager. It cannot be encapsulated as caching or other caching.
        template.setConnectionFactory(connectionFactory); 
        return template;
    }

    This is used to set up the containerFactory used by @jmsListener@Bean public JmsListenerContainerFactory<? > msgFactory(ConnectionFactory connectionFactory, DefaultJmsListenerContainerFactoryConfigurer configurer, PlatformTransactionManager transactionManager) { DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); factory.setTransactionManager(transactionManager); factory.setCacheLevelName("CACHE_CONNECTION");
        factory.setReceiveTimeout(10000L);
        configurer.configure(factory, connectionFactory);
        return factory;
    }

    @Bean
    public PlatformTransactionManager transactionManager(ConnectionFactory connectionFactory) {
        returnnew JmsTransactionManager(connectionFactory); }}Copy the code
@Service
public class CustomerService {
    private static final Logger LOG = LoggerFactory.getLogger(CustomerService.class);

    @Autowired
    JmsTemplate jmsTemplate;
    @Autowired
    private PlatformTransactionManager transactionManager;

    @PostConstruct
    public void init() {
        jmsTemplate.setReceiveTimeout(3000);
    }
    ## Native transactions
    @JmsListener(destination = "customer:msg:new", containerFactory = "msgFactory")
    public void handle(String msg) {
        LOG.debug("Get JMS message to from customer:{}", msg);
        String reply = "Replied - " + msg;
        jmsTemplate.convertAndSend("customer:msg:reply", reply);
        if (msg.contains("error")) { simulateError(); }}# # JmsTransactionManager transactions
    @JmsListener(destination = "customer:msg2:new", containerFactory = "msgFactory")
    public void handle2(String msg) {
        LOG.debug("Get JMS message2 to from customer:{}", msg);
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setTimeout(15);
        TransactionStatus status = transactionManager.getTransaction(def);
        try {
            String reply = "Replied-2 - " + msg;
            jmsTemplate.convertAndSend("customer:msg:reply", reply);
            if(! msg.contains("error")) {
                transactionManager.commit(status);
            } else {
                transactionManager.rollback(status);
            }
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }

    private void simulateError() {
        throw new RuntimeException("some Data error."); }}Copy the code

Spring local Transactions

Spring local transactions are tightly dependent on the underlying resource manager (such as database connections) and are limited to the current transaction resource. This transaction processing method does not depend on the application server, so the deployment is flexible but it cannot support the distributed transaction of multiple data sources.

  • The Spring container manages the life cycle of transactions
  • Called through the Spring transaction interface
  • The business code is independent of the implementation of a specific transaction

Using local transaction Demo in database connection:

public void transferAccount() { 
       Connection conn = null; 
       Statement stmt = null; 
       try{ 
           conn = getDataSource().getConnection(); 
           Set autocommit to false,
           If true, the database will treat each update as a transaction and commit it automatically
           conn.setAutoCommit(false);
           stmt = conn.createStatement(); 
           ## Reduce the amount in account A by 500
           stmt.execute("\ update t_account set amount = amount - 500 where account_id = 'A'");
           Increase the amount in account B by 500
           stmt.execute("\ update t_account set amount = amount + 500 where account_id = 'B'");
           ## Commit transaction
           conn.commit();
           ## Transaction commit: Both steps of the transfer were successful
       } catch(SQLException sqle){            
           try{ 
               An exception occurred, rollback is performed in this transaction
              conn.rollback();
               ## Transaction rollback: The two step operation of the transfer is completely undonestmt.close(); conn.close(); }catch(Exception ignore){ } sqle.printStackTrace(); }}Copy the code

Local transaction mechanism process diagram:

Spring external (global) transactions

  • The external transaction manager provides transaction management
  • The external manager is invoked through the Spring transaction interface
  • Get an instance of the external transaction manager using JNDI, etc
  • External transaction managers are typically provided by application servers, such as JBoss

What is the JNDI?

Reference ==> What exactly is JNDI?