preface

Atomikos is an open source transaction manager that provides value-added services for the Java platform. It is mainly used to handle cross-database transactions. For example, an instruction has write operations in both LIBRARIES A and B, and Atomikos can be used when the business requires atomicity of write operations in both libraries. Here I put together a demo of Spring and Atomikos, and illustrate atomikos with a case study.

The preparatory work

Development tool: IDEA

Databases: mysql, Oracle

The body of the

Source code address: github.com/qw870602/at…

How it works: See if a database is rolled back by artificially creating exceptions between writes to two libraries

Procedure: 1. Perform normal write operations and observe the changes in database values

2. Create exceptions between write operation statements and observe changes in database values

The project structure



As you can see from web. XML, the container only loads appliactionContext.xml, and the rest of the configuration file other than database.properties is useless, so if you want to configure it in your project, Just add the atomikos section of AppliactionContext.xml to your project

appliactionContext.xml

<? xml version="1.0" encoding="UTF-8"? > <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xmlns:p="http://www.springframework.org/schema/p"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="Http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"> <! -- Add data source information to the properties property file --> <context:property-placeholder location="classpath:database.properties"/ > <! -- XA mode --> <! MYSQL database configuration --> <bean id="mysqlDataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" destroy-method="close">
        <property name="uniqueResourceName" value="dataSource1"/>
        <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"/>
        <property name="xaProperties">
            <props>
                <prop key="URL">${mysql.qa.db.url}</prop>
                <prop key="user">${mysql.qa.db.user}</prop>
                <prop key="password">${mysql.qa.db.password}</prop>
            </props>
        </property>
        <property name="minPoolSize" value="10" />
        <property name="maxPoolSize" value="100" />
        <property name="borrowConnectionTimeout" value="30" />
        <property name="maintenanceInterval" value="60"/> </bean> <! -- ORACLE database configuration --> <bean id="oracleDataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" destroy-method="close">
        <property name="uniqueResourceName" value="dataSource2"/>
        <property name="xaDataSourceClassName" value="oracle.jdbc.xa.client.OracleXADataSource" />
        <property name="xaProperties">
            <props>
                <prop key="URL">${oracle.qa.db.url}</prop>
                <prop key="user">${oracle.qa.db.user}</prop>
                <prop key="password">${oracle.qa.db.password}</prop>
            </props>
        </property>
        <property name="minPoolSize" value="10" />
        <property name="maxPoolSize" value="100" />
        <property name="borrowConnectionTimeout" value="30" />
        <property name="maintenanceInterval" value="60" />
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <! --<property name="configLocation" value="classpath:mybatis-config-mysql.xml" />-->
        <property name="dataSource" ref="mysqlDataSource" />
        <property name="mapperLocations" >
            <list>
                <value>classpath*:/dao/*.xml</value>
            </list>
        </property>
    </bean>
    <bean id="sqlSessionFactoryOracle" class="org.mybatis.spring.SqlSessionFactoryBean"> <! --<property name="configLocation" value="classpath:mybatis-config.xml" />-->
        <property name="dataSource" ref="oracleDataSource" />
        <property name="mapperLocations"> <list> <value>classpath*:/daodev/*.xml</value> </list> </property> </bean> <! -- MyBatis inject sqlSessionFactory for different mapper --> <bean id="mysqlTransactionTestDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
        <property name="sqlSessionFactory" ref="sqlSessionFactory" />
        <property name="mapperInterface" value="com.xy.dao.MysqlTransactionTestDao" />
    </bean>
    <bean id="transactionTestDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
        <property name="sqlSessionFactory" ref="sqlSessionFactoryOracle" />
        <property name="mapperInterface" value="com.xy.dao.TransactionTestDao"/> </bean> <! -- Distributed transaction --> <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close">
        <property name="forceShutdown" value="true"/>
    </bean>
    <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
        <property name="transactionTimeout" value="300"/>
    </bean>
    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManager" ref="atomikosTransactionManager"/>
        <property name="userTransaction" ref="atomikosUserTransaction"/>
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager"/> <context:annotation-config/> <! --&lt; ! &ndash; Automatically scans all classes in the Controller package if @Controller is injected as bean &ndash; &gt; -- > <! --&lt; ! &ndash; Transaction management & NDash; &gt; --> <context:component-scan base-package="com.xy"/ > <! -- Register interceptor --> <! --<mvc:interceptors> <bean class="com.springmybatis.system.interceptor.MyInterceptor" />
    </mvc:interceptors>-->
</beans>Copy the code

Use JUnit4 for unit testing

package com.xy.controller;

import com.xy.daodev.TransactionTestService;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;

@ContextConfiguration(locations = {"classpath:applicationContext.xml"}) public class TransactionTestMain extends AbstractJUnit4SpringContextTests { @Autowired private TransactionTestService  transactionTestService; /** * multiple data sources in the same transaction */ @test public voidmultipleDataSource2() {
  transactionTestService.updateMultipleDataSource("1"."1", 100L,"1.6"); }}Copy the code

Service implementation. No abnormal operation is performed

@Service
public class TransactionTestServiceImpl implements TransactionTestService {
   @Autowired
 @Qualifier("mysqlTransactionTestDao")
 private MysqlTransactionTestDao mysqlTransactionTestDao;
 
 @Autowired
 @Qualifier("transactionTestDao") private TransactionTestDao transactionTestDao; */ @override @transactional public void updateMultipleDataSource(String deUserId, StringinUserid, long money, String STR) {/ / account 1 roll-out operation mysqlTransactionTestDao. DecreaseMoney (deUserId, money); //Integer.parseInt(str); 2 / / account into operation transactionTestDao. IncreaseMoney (inUserid, money); }}Copy the code

Mysql simulated amount is transferred out, Oracle simulated amount is transferred in

<update id="decreaseMoney" parameterType="java.util.Map">
    UPDATE fx1 SET amount=amount - #{1,jdbcType=BIGINT} WHERE id=#{0,jdbcType=VARCHAR}
</update>Copy the code
<update id="increaseMoney">
    UPDATE fx1 SET amount=amount + #{1,jdbcType=BIGINT} WHERE id=#{0,jdbcType=VARCHAR}
</update>
Copy the code

Mysql > alter database



Oracle Initial amount



Perform normal operations



Mysql > select * from ‘mysql’;



Oracle current amount



Turn on the masked exception making code

public void updateMultipleDataSource(String deUserId, String inUserid, long money, String STR) {/ / account 1 roll-out operation mysqlTransactionTestDao. DecreaseMoney (deUserId, money); Integer.parseInt("skg"); 2 / / account into operation transactionTestDao. IncreaseMoney (inUserid, money);
}  Copy the code

If the current amount of mysql and Oracle does not change, the transaction is successfully rolled back. Then check the log



The console was found to have printed out an exception and Atomikos called the rollback() method, which was confirmed from the log.