spring+atomikos+mysql8.0 completes distributed transactions

What is business?

A group of sql operations related to the same data source are either completed or failed.

What are the characteristics of transactions?

  • Atomicity: success or failure
  • Consistency: offset each other (e.g. seller + 200, buyer-200)
  • Isolation: no interference between two parallel transactions in theory
  • Persistence: irreversible

Isolation level of transaction

level Name Dirty reading Non repeatable reading Phantom reading Default database
1 Read not submitted yes yes yes
2 Read submitted no yes yes oracle or sqlServer
3 Repeatable read no no yes mysql
4 Serialization no no no

Higher isolation level, lower performance

  • Dirty read: transaction a read uncommitted data of transaction b
  • Unreal reading: one transaction is querying, the other is inserting or deleting

Local affairs

A single database when a session performs a set of related sql operations (atomicity)
Implementation: manual transaction programming and automatic transaction programming

Distributed transaction

Transaction participants, servers, data sources, transaction managers, distributed in different nodes, atomic operations will either succeed or fail,

Multiple databases, multiple sessions, global transaction manager

Solution: use unified global transaction manager to manage local transactions on both sides

Application architecture of distributed transaction

  • Single service distributed transaction single service multi library solution: global transaction manager
  • Solution of sub database and sub table (when data volume is large): middleware + Database Synchronization Technology
  • Multi database and multi service: solution: global database and Service Coordinator (service) coordinate together \ database resources and services

Solution classification

  • Rigid transaction
    Acid (local transaction) is applied to local area network or enterprise. Application scenario: order or log
  • Flexible transaction
    The distributed transaction management of CAP or BASE is applied to the Internet. Usage scenarios: payment, order, receipt, etc.

CAP

  • C: Data consistency
  • A: Data availability
  • P: Partition fault tolerance
    Conclusion: only two of the three conditions can be satisfied, and p is a must, so CAP has at most two conditions, CP and AP

Two points explanation

Data availability refers to the reasonable response returned by non fault nodes within a reasonable time

Reasonable time refers to 3s reasonable 3 hours unreasonable

Reasonable response means that it's unreasonable to process. Please wait a while. It's unreasonable to have a null pointer object

Partition fault tolerance: cluster availability
The network can not be 100% reliable, zoning is an inevitable phenomenon, P must ensure

BASE

Distributed transactions only need to achieve consistency eventually

  • BA: basically available
  • S: Soft status, such as order status: payment on behalf, paid, shipped, signed in, end
  • E: Final consistency

Flexible transaction solution

According to the theory of BASE, it's called flexible transaction

  • TCC two-stage submission compensatory
    Phase I: Determination
    Phase 2: commit the transaction if it succeeds, and roll back the transaction if it fails

  • Ensure the final consistency of transactions through messages
    Non transactional message middleware (activimq, rabbitmq, kafka)
    Transactional message rocketmq (in Alibaba message team)

  • Best effort notification

base theory is the extension of cap theory

DTP: a distributed transaction processing model

Five model elements are as follows

  • AP: Application
  • RM: Resource Manager: database file system and provides access to resources
  • TM: transaction manager: responsible for the unique identification of the assigned transaction, monitoring the execution progress of the transaction, and responsible for the transaction submission and rollback
  • CRM: communication resource manager: control a TM domain or cross TM domain distributed application communication
  • CP: communication protocol

What is XA?

xa is the interface between database and TM (transaction manager)

Relationship between XA specification and two phase protocol

XA: protocol and interface
The two-phase commit protocol is not proposed in the XA specification, but the XA specification defines the interface needed in the two-phase commit protocol

stage TCC XA
Stage 1 Request multiple business parties to reserve business resources Ask if each RM (Resource Manager) can commit a transaction
Stage 2: success Confirm resource operation of each business party Commit transaction branches on each RM
Stage 2: failure Cancel the execution of each task room resource operation Roll back transaction branches on each RM

xa is a set of interfaces
TCC is a set of protocols
XA TCC is cross referenced

Advantages of TCC in two phases: relatively simple implementation disadvantages: low performance
Application scenario: low performance requirements and strong data consistency

JTA, XA and atomikos

JTA is the Java implementation of XA

JTA specification is JAVA version of XA specification

The transaction manager in JTA is abstracted as java.transaction

JTA only defines interfaces
Realization:

  • J2EE container provides JTA implementation such as JBOSS
  • Independent JTA implementation: for example, JOTM, Atomikos. For Tomcat, Jetty, and regular Java applications
  • Atomikos:JTA implementation for Tomcat container

Next, use Atomikos+spring+mysql8.0 to implement a small example. The above is a simple basic knowledge. If there is something wrong, please correct it.

Analysis of atomikos steps

  • Configuration of data source
  • Configuration and global transaction manager
  • Integrating the global transaction manager into spring
  • Aspect management configuration and transaction notification
  • Manage pointcuts and transaction notifications

The project directory is shown in the figure below

spring project idea+maven

Simulation environment needs to prepare two separate databases
One is the transaction order database
One is the log database

  • Prepare data source

Prepare two databases: logtest and ordertest
There is a table loginfo under the logtest Library
order_info under ordertest Library

CREATE TABLE `loginfo` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `content` longtext,
  `createTime` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
CREATE TABLE `order_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `money` varchar(255) DEFAULT NULL,
  `userid` varchar(255) DEFAULT NULL,
  `address` varchar(255) DEFAULT NULL,
  `createTime` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
  • Preparing dependencies under pom files
<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <!-- Third party reliance commons-logging -->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <!--Aop Related dependence-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.5</version>
        </dependency>
        <!--Spring Core dependency module -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
      
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>javax.transaction</groupId>
            <artifactId>jta</artifactId>
            <version>1.1</version>
        </dependency>

        <dependency>
            <groupId>com.atomikos</groupId>
            <artifactId>atomikos-util</artifactId>
            <version>4.0.6</version>
        </dependency>
        <dependency>
            <groupId>com.atomikos</groupId>
            <artifactId>transactions</artifactId>
            <version>4.0.6</version>
        </dependency>
        <dependency>
            <groupId>com.atomikos</groupId>
            <artifactId>transactions-jta</artifactId>
            <version>4.0.6</version>
        </dependency>
        <dependency>
            <groupId>com.atomikos</groupId>
            <artifactId>transactions-jdbc</artifactId>
            <version>4.0.6</version>
        </dependency>
        <dependency>
            <groupId>com.atomikos</groupId>
            <artifactId>transactions-api</artifactId>
            <version>4.0.6</version>
        </dependency>
        <!--aop Dynamic proxy-->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib-nodep</artifactId>
            <version>3.2.5</version>
        </dependency>
        <!--Database connection pool-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.13</version>
        </dependency>

        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.1</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.2</version>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>

    </dependencies>

The data sources of the two libraries are configured in the jdbc.properties file

Special note: my local environment mysql version is 8.0.11, so the driver is different from 8.0 version

jdbc.driverClassName=com.mysql.cj.jdbc.Driver
#jdbc.url=jdbc:mysql://localhost:3306/ordertest?useUnicode=true&characterEncoding=utf8&autoReconnect=true  ?useUnicode=true&useJDBCCompliantTimezoneShift=true&serverTimezone=UTC&characterEncoding=utf8
jdbc.url=jdbc:mysql://localhost:3306/ordertest?useUnicode=true&useJDBCCompliantTimezoneShift=true&serverTimezone=UTC&characterEncoding=utf8 
jdbc.username=root
jdbc.password=root

jdbc.log.driverClassName=com.mysql.cj.jdbc.Driver 
jdbc.log.url=jdbc:mysql://localhost:3306/logtest?useUnicode=true&useJDBCCompliantTimezoneShift=true&serverTimezone=UTC&characterEncoding=utf8
#jdbc.log.url=jdbc:mysql://localhost:3306/logtest?useUnicode=true&characterEncoding=utf8&autoReconnect=true
jdbc.log.username=root
jdbc.log.password=root

Prepare entity class LogInfo.java OrderInfo.java

import java.io.Serializable;
import java.util.Date;

public class LogInfo implements Serializable {

    private Integer id;
    private Date createTime;
    private String content;
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }


    @Override
    public String toString() {
        return "LogInfo{" +
                "id=" + id +
                ", createTime=" + createTime +
                ", content='" + content + '\'' +
                '}';
    }
}

import java.io.Serializable;
import java.util.Date;

public class OrderInfo implements Serializable {

    private Integer id;
    private  String money;
    private String userid;
    private String address;
    private Date createTime;
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getMoney() {
        return money;
    }

    public void setMoney(String money) {
        this.money = money;
    }

    public String getUserid() {
        return userid;
    }

    public void setUserid(String userid) {
        this.userid = userid;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    @Override
    public String toString() {
        return "OrderInfo{" +
                "id=" + id +
                ", money=" + money +
                ", userid='" + userid + '\'' +
                ", address='" + address + '\'' +
                ", createTime=" + createTime +
                '}';
    }
}

Preparing LogInfoMapper.java is mainly about the function of adding logs

import org.apache.ibatis.annotations.Insert;

public interface LogInfoMapper {

    @Insert("insert into logtest.loginfo(id,createTime,content) value (#{id},#{createTime},#{content})")
    int add(LogInfo logInfo);
}

Preparing OrderInfoMapper.java is mainly about order adding function

import org.apache.ibatis.annotations.Insert;

@Service("orderInfoMapper")
public interface OrderInfoMapper {

    @Insert("insert into ordertest.order_info(id,money,userid,address,createTime) value (#{id},#{money},#{userid},#{address},#{createTime})")
    int add(OrderInfo orderInfo);
}

Prepare order related interface OrderInfoService.java

import org.springframework.stereotype.Component;
public interface OrderInfoService {
    int add(OrderInfo orderInfo);
}

Prepare order related interface implementation class OrderInfoServiceImpl.java

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import sun.rmi.runtime.Log;

import java.util.Date;

@Service
public class OrderInfoServiceImpl implements OrderInfoService {
    @Autowired
    private OrderInfoMapper orderInfoMapper;


    @Autowired
    private LogInfoMapper logInfoMapper;

//    @Transactional
    public int add(OrderInfo orderInfo) {
        int i =  orderInfoMapper.add(orderInfo);
        System.out.println("insert orderinfo The data affected are==="+i);

//        Test case 2 exception occurs here rollback occurs
//        int error =10/0;
        LogInfo logInfo = new LogInfo();
        logInfo.setId((int)Math.random()*1000);
        logInfo.setContent(orderInfo.toString());
        logInfo.setCreateTime(new Date());
        int j = logInfoMapper.add(logInfo);
        System.out.println("insert logInfo The data affected are==="+j);

//        After test case 3 is successfully inserted, an exception occurs here and a rollback occurs
        int error =10/0;
       return 0;
    }
}

Add configuration file applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
xmlns:tx="http://www.alibaba.com/schema/stat"
http://www.alibaba.com/schema/stat http://www.alibaba.com/schema/stat.xsd
-->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx
		http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
	  <!--Because it's useless. springBoot Manual dependency injection here OrderInfoServiceImpl-->	
    <bean id="OrderInfoService" class="zln.atomikosTest.service.impl.OrderInfoServiceImpl"/>
    <!--Load profile guarantee jdbc.properties Under the same folder as the current file-->
    <context:property-placeholder location="jdbc.properties"/>
    <!--XML Configure automatic scanning, and change it to your own path -->
    <context:component-scan base-package="zln.service ,zln.atomikosTest"/>

    <!--Configuration data source I use here mysql8.0.1 Low version xaDataSourceClassName Of value Convert value to com.mysql.jdbc.jdbc2.optional.MysqlXADataSource-->
    <bean id="abstractXADataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close" abstract="true">
        <property name="xaDataSourceClassName" value="com.mysql.cj.jdbc.MysqlXADataSource"/>
        <property name="poolSize" value="10"/>
        <property name="minPoolSize" value="10"/>
        <property name="maxPoolSize" value="30"/>
        <property name="borrowConnectionTimeout" value="60"/>
        <property name="reapTimeout" value="20"/>
        <property name="maxIdleTime" value="60"/>
        <property name="maintenanceInterval" value="60"/>
        <property name="testQuery">
            <value>select 1</value>
        </property>
    </bean>

    <!--Basic database configuration information-->
    <bean id="dataSourceOne" parent="abstractXADataSource">
        <property name="uniqueResourceName">
            <value>dataSourceOne</value>
        </property>
        <!--Database driven com.mysql.jdbc.jdbc2.optional.MysqlXADataSource-->
        <!-I use it here mysql8.0.1 Low version xaDataSourceClassName Of value Convert value to com.mysql.jdbc.jdbc2.optional.MysqlXADataSource-->
        <property name="xaDataSourceClassName" value="com.mysql.cj.jdbc.MysqlXADataSource"/>
        <property name="xaProperties">
            <props>
                <prop key="URL">${jdbc.url}</prop>
                <prop key="user">${jdbc.username}</prop>
                <prop key="password">${jdbc.password}</prop>
            </props>
        </property>
    </bean>

    <!--Log data source-->
    <!--Basic database configuration information-->
    <bean id="dataSourceLog" parent="abstractXADataSource">
        <property name="uniqueResourceName">
            <value>dataSourceLog</value>
        </property>
        <!--Database driven-->
        <!-I use it here mysql8.0.1 Low version xaDataSourceClassName Of value Convert value to com.mysql.jdbc.jdbc2.optional.MysqlXADataSource-->
        <property name="xaDataSourceClassName" value="com.mysql.cj.jdbc.MysqlXADataSource"/>
        <property name="xaProperties">
            <props>
                <prop key="URL">${jdbc.log.url}</prop>
                <prop key="user">${jdbc.log.username}</prop>
                <prop key="password">${jdbc.log.password}</prop>
            </props>
        </property>
    </bean>

    <!--sqlSessionFactoryBean-->
    <bean id="sqlSessionFactoryBeanOne" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- Injection connection pool -->
        <property name="dataSource" ref="dataSourceOne"/>
         <!--value It's worth changing to your own path-->
        <property name="typeAliasesPackage" value="zln.atomikosTest.domain"></property>
    </bean>
    <!--Data log sqlSessionFactoryBean-->
    <bean id="sqlSessionFactoryBeanLog" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- Injection connection pool -->
        <property name="dataSource" ref="dataSourceLog"/>
          <!--value It's worth changing to your own path-->
        <property name="typeAliasesPackage" value="zln.atomikosTest.domain"></property>
    </bean>


    <!--Package scan order's-->
    <bean id="mapperone" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
      <!--value It's worth changing to your own path-->
        <property name="basePackage" value="zln.atomikosTest.mapper"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBeanOne"></property>
    </bean>
    <!--Of the package scan log-->
    <bean id="mapperLog" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
      <!--value It's worth changing to your own path-->
        <property name="basePackage" value="zln.atomikosTest.logmapper"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBeanLog"></property>
    </bean>

    <!--To configure atomikos Transaction manager-->
    <bean id="atomiksTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close">
        <property name="forceShutdown" value="false"/>
    </bean>
    <!--Configure local transaction manager-->
    <bean id="atomiksUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
        <property name="transactionTimeout" value="300000"/>
    </bean>

    <!--jta Transaction manager-->
    <bean id="springTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManager" ref="atomiksTransactionManager"/>
        <property name="userTransaction" ref="atomiksUserTransaction"/>
        <property name="allowCustomIsolationLevels" value="true"/>
    </bean>


    <!--Declarative transaction aop Configuration configuration aop Pointcut expression-->
    <aop:aspectj-autoproxy/>
    <!--Use cglib Dynamic proxy-->
    <tx:annotation-driven transaction-manager="springTransactionManager" proxy-target-class="true"/>
    <!--Configure transaction notifications-->
    <tx:advice id="txAdvice" transaction-manager="springTransactionManager">
        <tx:attributes>
            <tx:method name="add*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.Exception"/>
            <tx:method name="save*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.Exception"/>
            <tx:method name="insert*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.Exception"/>
            <tx:method name="update*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.Exception"/>
            <tx:method name="modify*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.Exception"/>
            <tx:method name="delete*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.Exception"/>
            <tx:method name="query*" read-only="true"/>
            <tx:method name="select*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    <aop:config>
     	<!--expression Just switch to your own path-->
        <aop:pointcut id="tranpointcut" expression="execution(* zln.atomikosTest.service.impl.*.*(..))" />
        <aop:advisor advice-ref="txAdvice" pointcut-ref="tranpointcut"/>
    </aop:config>
</beans>

Start testing

    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        OrderInfoService orderInfoService = (OrderInfoService)applicationContext.getBean("OrderInfoService");
//        System.out.println(orderInfoService);

        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setId((int)Math.random()*1000);
        orderInfo.setMoney("19888");
        orderInfo.setAddress("Chaoyang District, Changchun");
        orderInfo.setUserid("ceshi001");
        orderInfo.setCreateTime(new Date());
        orderInfoService.add(orderInfo);
    }

There are three test cases for testing
The first one is that two databases insert data without any errors



Abnormal order table after inserting the second order table roll back the order table

Create an error simulation scenario after order insertion
int error =10/0;

After execution, the order library is inserted, but there is an exception. Observe that the database has no new data to prove that the rollback is successful

The third kind of order table and log table are both inserted, and the order table and log table are both rolled back
results of enforcement

Although it was inserted at the beginning, there was an exception before the method was executed. Both libraries rolled back and failed to insert

The above is the simple theory and code implementation of distributed transaction completed by spring+atomikos+mysql8.0, and the code is easy to test and use.
The knowledge in this scenario is simple to simulate the distributed transaction under single business and multiple databases
The main application of atomikos to complete theoretical knowledge is to show you the code implementation
The aop of spring is mainly used to manage transactions
The above is that after taking a note recently, there will be more in-depth content to be updated.
Finally, we welcome you to put forward corrections to the article.

Published 3 original articles, won 0 praise and visited 37
Private letter follow

Keywords: JDBC Java Spring MySQL

Added by charliepage on Fri, 28 Feb 2020 11:14:46 +0200