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.