Introduction: In the last article, distributed transaction based on reliable message scheme: Lottor introduction introduced the common solution of distributed transaction and the principle of distributed transaction component Lottor based on reliable message scheme implemented by the author, and showed the application of console management. Before formally introducing the concrete implementation of Lottor, this article will first cover transaction management in Java, specifically Spring. PS: Many readers have asked whether Lottor is open source. Here is a unified answer: Yes, Lottor is currently being used in my company’s internal project, and I am refactoring the coupled business code. We will update it to GitHub in the next article. This article is long and suitable for computer reading. If you are already familiar with transactions in Java, you may want to skip this article. Welcome to this series of articles.

Type of Java transaction

Transaction management is an essential part of application system development. There are three types of Java transactions: JDBC transactions, JTA(Java Transaction API) transactions, and container transactions. Common container transactions such as Spring transactions, container transactions are mainly provided by J2EE application servers, and container transactions are mostly done based on JTA, which is a fairly complex API implementation based on JNDI. I’ll start with two transactions in J2EE development: JDBC transactions and JTA transactions.

JDBC transaction

All of JDBC’s behavior includes transactions that are based on a Connection. In JDBC, transactions are managed through Connection objects. In JDBC, the common transaction-related methods are: setAutoCommit, COMMIT, rollback, and so on.

By default, when we create a database connection, we run it in auto-commit mode. This means that any time we complete an SQL execution, the transaction is committed automatically. So each SQL we execute is a transaction, and if we are running DML or DDL statements, these changes are stored in the database at the end of each SQL statement. Sometimes we want a set of SQL statements to be part of a transaction, so that we can commit when all the statements run successfully, and if any exceptions occur, we can choose to roll them all back as part of the transaction.

public static void main(String[] args) {
        Connection con = null;
        try {
            con = DBConnection.getConnection();
            con.setAutoCommit(false);
            insertAccountData(con, 1."Pankaj");
            insertAddressData(con, 1."Albany Dr"."San Jose"."USA");
        }
        catch (SQLException | ClassNotFoundException e) {
            e.printStackTrace();
            try {
                con.rollback();
                System.out.println("JDBC Transaction rolled back successfully");
            }
            catch (SQLException e1) {
                System.out.println("SQLException in rollback"+ e.getMessage()); }}finally {
            try {
                if(con ! =null)
                    con.close();
            }
            catch(SQLException e) { e.printStackTrace(); }}}Copy the code

The above code implements a simple function to insert account and address information and control the insertion through transactions, either committing or rolling back.

JDBC provides the most basic support for database transactions using Java. With JDBC transactions, we can put multiple SQL statements into the same transaction, ensuring that a JDBC transaction cannot span multiple databases! As a result, JDBC transactions cannot be used if multi-database operations or distributed scenarios are involved.

JTA transaction

In general, JDBC transactions can solve problems such as data consistency. Given their relatively simple usage, many people only know about TRANSACTIONS in Java as JDBC transactions, or they know about transactions in frameworks such as Hibernate, Spring, etc. However, since DISTRIBUTED transactions cannot be implemented by JDBC, and there are more and more distributed scenarios today, JTA transactions have emerged.

JTA(Java Transaction API), the Java Transaction API allows applications to perform distributed transactions, that is, transactions can access or update computer resources on two or more networks. JTA specifies the standard Java interface between the transaction manager and the parties involved in a distributed transactional system: the application, the application server, and the resource manager that controls access to the shared resources affected by the transaction. A transaction defines a logical unit of work that either succeeds completely or produces no results at all.

Java Transaction Programming Interface (JTA: Java Transaction API) and Java Transaction Services (JTS; Java Transaction Service provides distributed Transaction services for the J2EE platform. Distributed transactions include Transaction Managers and one or more Resource Managers that support the XA protocol. We can think of the resource manager as an arbitrary type of persistent data store; The transaction manager is responsible for the coordination and control of all transaction participation units. JTA transactions effectively shield the underlying transaction resources so that applications can participate in transaction processing in a transparent way. However, compared with local transactions, XA protocol has a high system overhead, so whether distributed transactions are really needed should be considered carefully during system development. If you really need distributed transactions to coordinate multiple transaction resources, you should implement and configure XA supported transaction resources, such as JMS, JDBC database connection pooling, and so on.

JTA provides Java inside. Transaction. UserTransaction, it defines the following methods:

  • Begin ()- Starts a distributed Transaction (in the background TransactionManager creates a Transaction object and associates it with the current thread via ThreadLocale)
  • Commit ()- Commit the transaction (in the background TransactionManager will fetch the transaction object from the current thread and commit the transaction it represents)
  • Rollback ()- Rollback transactions (In the background TransactionManager pulls the transaction object from the current thread and rolls back the transaction it represents)
  • GetStatus ()- Returns the Status of distributed transactions associated with the current thread (the Status object defines all transaction states)
  • SetRollbackOnly ()- Indicates that distributed transactions associated with the current thread will be rolled back

It is important to note that the JTA does not use UserTransaction to convert a normal JDBC operation directly to a JTA operation. JTA requires DataSource, Connection, and Resource to comply with the XA specification. Only classes that implement the relevant interfaces of the XA specification can participate in JTA transactions.

The XA specification, which readers can add from the previous article, is now supported by major databases. The process used is as follows:

To use JTA transactions, you need a JDBC driver that implements the Javax.sql. XADataSource, javax.sql.XAConnection, and javax.sql.XAResource interfaces. A driver that implements these interfaces will be able to participate in JTA transactions. An XADataSource object is a factory of XAConnection objects. XAConnection is a JDBC connection that participates in a JTA transaction.

To use JTA transactions, you must use XADataSource to generate a database connection, resulting in an XA connection.

The difference between XA connections (Javax.sql.xaconnection) and non-XA connections (java.sql.Connection) is that XA can participate in JTA transactions and does not support automatic commit.

In addition to database, JBoss, JMS middleware ActiveMQ and many other components comply with XA protocol, 2PC and 3PC also comply with XA specification. More details on JTA will not be covered in this article, but there will be an opportunity to focus on JTA transactions in depth.

Spring Transaction Management

Spring supports both programmatic and declarative transaction management. Spring all transaction management strategy class inherits from PlatformTransactionManager interface, the class diagram is as follows:

public interface PlatformTransactionManager {
    TransactionStatus getTransaction(TransactionDefinition var1) throws TransactionException;

    void commit(TransactionStatus var1) throws TransactionException;

    void rollback(TransactionStatus var1) throws TransactionException;
}
Copy the code

The #getTransaction(TransactionDefinition) method returns the currently active transaction or creates a new transaction based on the specified propagation behavior. The parameters in this method are the TransactionDefinition class, which defines some basic transaction properties. The other two methods, commit and rollback, pass in the TransactionStatus TransactionStatus value.

The transaction attribute

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    @AliasFor("transactionManager")
    String value(a) default "";

    @AliasFor("value")
    String transactionManager(a) default "";
	// Define transaction propagation rules
    Propagation propagation(a) default Propagation.REQUIRED;
	// Specify the isolation level of the transaction
    Isolation isolation(a) default Isolation.DEFAULT;
	// Define a timeout for long running transactions
    int timeout(a) default- 1;
	// Specifies that the transaction is read-only
    boolean readOnly(a) default false;
	Rollback-for specifies which checking exceptions the transaction should rollback without committing
    Class<? extends Throwable>[] rollbackFor() default {};
	// An array of exception class names that cause the transaction to roll back
    String[] rollbackForClassName() default {};
	// no-rollback-for specifies which exceptions the transaction should continue to execute without rolling back
    Class<? extends Throwable>[] noRollbackFor() default {};
	// An array of exception class names that will not cause the transaction to roll back
    String[] noRollbackForClassName() default {};
}
Copy the code

You can use @Transactional to define transaction attributes. Transaction attributes describe how transaction policies are applied to methods. Transaction attributes contain five aspects:

  • Propagation behavior
  • Isolation level
  • Rollback rules
  • Transaction timeout
  • Whether the read-only

Spring transactions propagate properties

Propagation behavior defines the transaction boundary between the client and the called method, that is, propagation rules answer the question of whether a new transaction should be started or suspended, or whether a method should run in a transactional environment. Spring defines seven constants that begin with PROPAGATION_ to denote its propagation properties. TransactionDefinition defines the following constants:

The name of the value explain
PROPAGATION_REQUIRED 0 Supports current transactions and creates a new one if there are none. This is the most common choice and is the propagation of transactions by Spring’s default.
PROPAGATION_SUPPORTS 1 Current transactions are supported, and non-transactionally executed if there are none.
PROPAGATION_MANDATORY 2 Supports current transactions and throws an exception if there are none.
PROPAGATION_REQUIRES_NEW 3 Create a new transaction and suspend the current transaction if one exists.
PROPAGATION_NOT_SUPPORTED 4 Performs the operation nontransactionally, suspending the current transaction if one exists.
PROPAGATION_NEVER 5 Executes nontransactionally, throwing an exception if a transaction currently exists.
PROPAGATION_NESTED 6 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.

Spring Isolation Level

The isolation level defines the extent to which a transaction may be affected by other concurrent transactions. Multiple concurrent transactions may cause various read phenomena, such as dirty reads, phantom reads, and unrepeatable reads. TransactionDefinition defines the following constants:

fruit The price The number of
ISOLATION_DEFAULT – 1 This is a PlatfromTransactionManager default isolation level, using the database to the default transaction isolation level. The other four correspond to JDBC isolation levels
ISOLATION_READ_UNCOMMITTED 1 This is the lowest isolation level for a transaction and allows another transaction to see the uncommitted data of that transaction. This isolation level produces dirty reads, unrepeatable reads, and phantom reads.
ISOLATION_READ_COMMITTED 2 Ensure 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.
ISOLATION_REPEATABLE_READ 4 This transaction isolation level prevents dirty, non-repeatable reads. But illusions can occur.
ISOLATION_SERIALIZABLE 8 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 to avoid magic read.

Whether the read-only

If the transaction only reads to the back-end database, the database can take advantage of the transaction ID read-only feature for specific optimizations. By making the transaction read-only, you give the database an opportunity to apply any optimizations it sees fit. Because read-only or not is enforced by the database when a transaction is started, only methods with propagating behavior (PROPAGATION_REQUIRED,PROPAGATION_REQUIRED_NEW,PROPAGATION_NESTED) that can start a new transaction, Make sense.

Transaction timeout

In order for the application to run well, transactions should not run for too long. Because the timeout clock starts at the start of a transaction, it only makes sense for methods that have propagation behavior (ibid.) that might start a new transaction. The default value is the timeout value of the underlying transaction system, or none if the underlying database transaction system has no timeout value.

Transaction rollback

The transaction rollback rule defines which exceptions cause a transaction to roll back and which do not. By default, transactions are rolled back only when they encounter run-time exceptions and not when they encounter checker exceptions. Throwing an exception that is a subclass of RuntimeException (Errors also causes the transaction to roll back) and throwing a Checked exception does not. You can explicitly configure the transaction to be rolled back when those exceptions are thrown, including checked. You can also explicitly define those exceptions that do not roll back transactions when thrown. It is also possible to indicate that a transaction must be rolled back through the programmed setRollbackOnly() method, which is the only operation that can be performed after calling setRollbackOnly().

Configure the Spring transaction manager

Spring uses XML to do the following configuration:

 <! -- Configure transaction manager -->  
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
        <property name="dataSource" ref="dataSource" />  
    </bean>  
Copy the code

If it is Spring the Boot, we only need to set up the DataSource, DataSourceTransactionManagerAutoConfiguration automatically configured DataSourceTransactionManager.

		@Bean
		@ConditionalOnMissingBean(PlatformTransactionManager.class)
		public DataSourceTransactionManager transactionManager( DataSourceProperties properties) {
			DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(
					this.dataSource);
			if (this.transactionManagerCustomizers ! =null) {
				this.transactionManagerCustomizers.customize(transactionManager);
			}
			return transactionManager;
		}
Copy the code

After configuring the transaction manager, the transaction must be handled by ourselves. Spring provides two methods of transaction management: programmatic transaction management and declarative transaction management.

Programmatic transaction management

Programmatic transaction management through PlatformTransactionManager we can achieve to transaction management, the same Spring also provides us with a template class TransactionTemplate for transaction management, the following template class are mainly introduced.

Template class

We need to configure in the configuration file:

    <! -- Configure transaction management template -->
    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="transactionManager"></property>
        <! -- Define the transaction isolation level,-1 means use the default database level -->
        <property name="isolationLevelName" value="ISOLATION_DEFAULT"></property>
        <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"></property>
    </bean>
Copy the code

TransactionTemplate helps us encapsulate a lot of code and saves us a lot of work. A special account table was created to simulate a saving scenario.

    //BaseSeviceImpl.java
    @Override
    public void insert(String sql, boolean flag) throws Exception {
        dao.insertSql(sql);
        // If flag is true, an exception is thrown
        if (flag) {
            throw new Exception("has exception!!!"); }}// Get the total amount
    @Override
    public Integer sum(a) {
        return dao.sum();
    }
Copy the code
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-test.xml"})
public class TransactionTest{
    @Resource
    private TransactionTemplate transactionTemplate;
    @Autowired
    private BaseSevice baseSevice;

    @Test
    public void transTest(a) {
        System.out.println("before transaction");
        Integer sum1 = baseSevice.sum();
        System.out.println("before transaction sum: "+sum1);
        System.out.println("transaction....");
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                try {
                    baseSevice.insert("INSERT INTO account VALUES (100);".false);
                    baseSevice.insert("INSERT INTO account VALUES (100);".false);
                } catch(Exception e) { status.setRollbackOnly(); e.printStackTrace(); }}}); System.out.println("after transaction");
        Integer sum2 = baseSevice.sum();
        System.out.println("after transaction sum: "+sum2); }}Copy the code

When an Exception of type Exception is thrown and needs to be rolled back, you need to catch the Exception and tell the transaction manager that the current transaction needs to be rolled back by calling the setRollbackOnly() method of the Status object.

Declarative transaction management

There are two common approaches to declarative transaction management: XML configuration files based on TX and AOP namespaces, and @Transactional annotations. As Spring and Java versions become more advanced, annotations become more common.

XML configuration files based on tx and AOP namespaces

<tx:advice id="advice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="insert" propagation="REQUIRED" read-only="false"  rollback-for="Exception"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="pointCut" expression="execution (* com.blueskykong.service.*.*(..) )"/>
        <aop:advisor advice-ref="advice" pointcut-ref="pointCut"/>
    </aop:config>
Copy the code
    @Test
    public void transTest(a) {
        System.out.println("before transaction");
        Integer sum1 = baseSevice.sum();
        System.out.println("before transaction sum: "+sum1);
        System.out.println("transaction....");
        try{
            baseSevice.insert("INSERT INTO account VALUES (100);".true);
        } catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("after transaction");
        Integer sum2 = baseSevice.sum();
        System.out.println("after transaction sum: "+sum2);
    }
Copy the code

Based on the @Transactional annotation

The most common way is also the simple way. Just turn on support for annotation transaction management in the configuration file.

<tx:annotation-driven transaction-manager="transactionManager"/>
Copy the code

And Spring boot startup class must open transaction, and @ EnableTransactionManagement is the open transaction with annotations.

    @Transactional(rollbackFor=Exception.class)
    public void insert(String sql, boolean flag) throws Exception {
        dao.insertSql(sql);
        // If flag is true, an exception is thrown
        if (flag){
            throw new Exception("has exception!!!"); }}Copy the code

Rollback specifies the rollback of exceptions. By default, non-checking exceptions, including error, are automatically rolled back.

conclusion

This article mainly introduces the types of Java transactions: JDBC transactions, JTA(Java Transaction API) transactions, container transactions. JDBC transactions and JTA transactions are briefly introduced, and the transaction management of Spring is introduced in detail. Spring implements transaction management through transaction manager. Spring provides many built-in transaction manager implementation, such as data transaction manager DataSourceTransactionManager, integrate JPA JpaTransactionManager transaction management, etc. We introduced the five aspects of the Spring transaction attribute and the two methods of transaction management provided by Spring: programmatic and declarative transaction management. Among them, declarative transaction management is the most convenient and more widely used. This article is intended to familiarize the reader with transactions in Java when dealing with distributed transactions. JTA transactions, in fact, also introduced the concept of distributed transactions, corresponding to 2PC and 3PC XA specifications.

Recommended reading

Distributed transactions based on reliable message schemes

Subscribe to the latest articles, welcome to follow my official account

reference

  1. Spring’s transaction management mechanism
  2. JTA Deep Adventures – Principles and implementation
  3. Transactions in Java — JDBC transactions and JTA transactions
  4. Spring Transaction Management details