mysql和neo4j整合多資料來源和事務

2023-06-15 15:00:34

在微服務大行其道的今天,按理說不應該有多資料來源這種問題(嗯,主從庫算是一個多資料來源的很常見的場景。),但是也沒人規定不能這樣做。
就算有人規定的,曾經被奉為圭臬的資料庫三大正規化現在被寬表衝得七零八落,在很多場景下,其實是鼓勵建立冗餘欄位的。


話說專案中需要用到圖資料庫,我們選用了Neo4j。

什麼是圖資料庫,大概提一下。
眾所周知,計算機裡的圖大部份情況下指的是graph而非picture。
圖資料庫使用圖形化的模型進行查詢,通過節點、邊和屬性等方式來表示和儲存資料,支援增刪改查(CRUD)等操作。
比如人與人之間的社群網路關係就是一個非常典型的圖資料場景。每個人看作是一個節點,節點之間聯絡成邊,關係(父子,單向雙向關注,上下級等)就是屬性。
在只有兩層關係的時候,mysql比起相簿更有優勢(節點數不很大的情況),但一旦層級達到3級以上,層級越高,相簿效率優勢越明顯。

一開始,Neo4j服務作為一個單獨的微服務,暴露增刪改查的功能出來。
公司業務是特殊的2G場景,全內網部署,在一個非常特殊的場景下,同事把Neo4j的功能直接整合到了主專案當中,這樣就涉及到了同一個專案當中的多資料來源以及相應的事務管理的問題。
這裡權當做個記錄。


多資料來源設定


spring boot 2.7通過spring-boot-starter-data-neo4j整合進來的neo4j版本在4.+,需要jdk11, 我們的生產環境還停留在jdk8,
所以這裡採用spring boot 2.0.1.RELEASE或者高版本的spring boot自定義版本的neo4j starter。
neo4j-jdbc-driver為3.4.0。

如果是不同的mysql/oracel資料庫之間的多資料來源設定還好一點,neo4j與spring boot的整合有很多種方法。
官方推薦直接使用driver,獲取session進行資料庫操作。此種方式,neo4j資料庫連線實現了AutoCloseable,無需關閉,也沒有連線池的概念。

但這樣的話就得手動實現事務。所以考慮再三還是採用和mysql一樣的,jdbc+mybatis的方式。

直接上程式碼吧。


連線設定資訊


就是兩個不同mysql資料庫和一個neo4j資料庫的連線設定。
既然是jdbc連線,那麼neo4j的連線URL就是jdbc:neo4j:bolt://192.168.124.125:7687,然後驅動不一樣,其它都是一樣的。

點選檢視程式碼
server.port = 8657
spring.datasource.pri.url = jdbc:mysql://192.168.1.1:3306/test?useSSL=false&characterEncoding=utf8&allowMultiQueries=true
spring.datasource.pri.driver-class-name = com.mysql.cj.jdbc.Driver
spring.datasource.pri.type = com.zaxxer.hikari.HikariDataSource
spring.datasource.pri.initial-size = 30
spring.datasource.pri.max-active = 101
spring.datasource.pri.min-idle = 7
spring.datasource.pri.max-wait = 60000
spring.datasource.pri.password = 123456
spring.datasource.pri.username = root
spring.datasource.pri.druid.filters=stat,slf4j

spring.datasource.sec.url = jdbc:neo4j:bolt://192.168.1.3:7687
spring.datasource.sec.driver-class-name =  org.neo4j.jdbc.bolt.BoltDriver
spring.datasource.sec.type = com.alibaba.druid.pool.DruidDataSource
spring.datasource.sec.password = 123456
spring.datasource.sec.username = neo4j
spring.datasource.sec.druid.filters=stat,slf4j

spring.datasource.thr.url = jdbc:mysql://192.168.1.2:3306/test?useSSL=false&characterEncoding=utf8&allowMultiQueries=true
spring.datasource.thr.driver-class-name = com.mysql.cj.jdbc.Driver
spring.datasource.thr.type = com.alibaba.druid.pool.DruidDataSource
spring.datasource.thr.initial-size = 30
spring.datasource.thr.max-active = 101
spring.datasource.thr.min-idle = 7
spring.datasource.thr.max-wait = 60000
spring.datasource.thr.password = 123456
spring.datasource.thr.username = root
spring.datasource.thr.druid.filters=stat,slf4j

mybatis.mapper-locations = classpath:mapper/*.xml
mybatis.configuration.cache-enabled = false
mybatis.configuration.local-cache-scope = SESSION

這裡只貼關鍵程式碼,重要的是思路。
文末有完整程式碼連結。


資料來源設定


mysql1

mysql1設定在MybatisDsMysql1Config

@Configuration
@MapperScan(basePackages = "com.nyp.dao.mapper1", sqlSessionFactoryRef = "sqlSessionFactory1")
public class MybatisDsMysql1Config {

    @Bean(name = "ds1")
    @ConfigurationProperties(prefix = "spring.datasource.pri")
    @Primary
    public DataSource ds1DataSource() {
        return new DruidDataSource();
    }

    @Bean(name = "sqlSessionFactory1")
    @Primary
    public SqlSessionFactoryBean sqlSessionFactory1(@Qualifier("ds1") DataSource dataSource) throws IOException {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        // 設定資料來源
        sqlSessionFactoryBean.setDataSource(dataSource);
        // 設定MyBatis的組態檔
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
        return sqlSessionFactoryBean;
    }

    @Bean("mysqlTransactionManager")
    public DataSourceTransactionManager transactionManager(@Qualifier("ds1") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

這裡差不多做了4件事。

  • 建立了名為ds1型別為DruidDataSource的DataSource
  • 根據DataSource建立SqlSessionFactoryBean
  • 給ds1資料來源建立一個事務管理器DataSourceTransactionManager
  • @MapperScan(basePackages = "com.nyp.dao.mapper1", sqlSessionFactoryRef = "sqlSessionFactory1") 通過basePackages指定了ds1資料庫的mybatis對映檔案的目錄,同時將sqlSessionFactory1注入。換句話說,凡是存取這個對映目錄的操作,就是存取ds1資料庫。 3個不同的資料庫,分別對應3個不同的mapper目錄。

網上很多的多資料來源的設定,通過DataSourceContextHolder來儲存多個資料來源,然後手動切換或者AOP註解切換資料來源,個人覺得這個操作挺麻煩的。


mysql2

現在已經設定好了mysql1的資料來源,mysql2類似,複製一個MybatisDsMysql1Config出來做為MybatisDsMysql2Config.
將裡面的所有bean name全部改掉,springboot全容器範圍保持唯一。


neo4j

現在mysql1,2資料來源都設定好了。設定MybatisDsNeo4jConfig的資料來源跟上面一模一樣。
不同的是事務管理器用DataSourceTransactionManager還是Neo4jTransactionManager。

    @Bean("transactionManager")
    @Primary
    public DataSourceTransactionManager neo4jTransactionManager(@Qualifier("dsNeo4j") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean("transactionManager2")
    @Primary
    public Neo4jTransactionManager neo4jTransactionManager(SessionFactory sessionFactory) {
        return new Neo4jTransactionManager(sessionFactory);
    }

如果要使用mysql和neo4j的混合事務那就要用DataSourceTransactionManager,因為neo4j存取使用的jdbc,如果事務管理器使用的是非jdbc的事務管理器,那neo4j的事務就不生效了。
如果不用混合事務,這裡可使用 Neo4jTransactionManager,但最好是使用DataSourceTransactionManager,畢竟連線方式是JDBC。

然後再建3個mybatis mapper介面目錄,不同的目錄對應相應的資料庫。

多資料來源設定就此完成,經測試沒有問題。


事務


可能有人要問了,之前Neo4j作為微服務的時候,按理說要有分散式事務的,不然怎麼保證兩邊操作的一致性呢?

其實是沒有新增分散式事務的。

首先相簿呼叫是在最後面的操作,如果相簿微服務呼叫返回非200的狀態碼,就丟擲異常,事務回滾。
但如果是在中間或前面呼叫,比如先呼叫相簿服務,再操作mysql失敗,那麼相簿就無法回滾了。
這樣的話還有跑批的操作,這步勉強算是一個最終一致性的操作吧。

  • 分散式領域,最終一致性,只會遲到,不會缺席。
  • 分散式的盡頭是最終一致性。

關於事務的測試的統一說明。
spring @Test為了防止測試資料汙染資料庫,此時新增@Transactional的時候,預設@Rollback(value = true),即永遠回滾。
所以最好新增一個介面進行測試。
如果非要用@Test測試,注意手動新增@Rollback並設定相應的value。


ChainedTransactionManager


ChainedTransactionManager並不能保證跨事務資料的原子性。官方的說法是在特定領域有用,而且不打算修復或者擴充套件。

spring 官方在2020年11月已經開始將ChainedTransactionManager標記為@Deprecated。這一決定引起了一些討論。具體情況在這裡。
https://github.com/spring-projects/spring-data-commons/issues/2232

有人在問,移除了過後,面對多資料來源的事務問題應該怎麼處理?這樣不管不顧就移除啦,這一點都不酷。

spring-data的leader在後面的解答,重點是,spring cannot help。
機翻,將就看。

引入一個假裝協調分散式事務的實用程式從一開始就是錯誤的。現在人們正在使用ChainedTransactionManager,假設它正確地做事情,並想知道為什麼沒有替代品。
遷移路徑可以是完全使用XA和2PC(例如,使用帶有JTA的Atomikos Transaction Manager)。或者,您可以以考慮部分提交的事務的方式設計業務程式碼。由於這是高度特定於您的領域的,因此Spring在這裡無法提供幫助。


我自己測試了一下

@Configuration
public class ChainedTransactionManagerConfig {

    @Autowired
    private DataSourceTransactionManager mysqlTransactionManager;
    @Autowired
    private DataSourceTransactionManager mysqlTransactionManager3;

    @Bean(name = "multiTransactionManager")
    @DependsOn("sessionFactory")
    public ChainedTransactionManager multiTransactionManager() {
        return new ChainedTransactionManager(mysqlTransactionManager,mysqlTransactionManager3);
    }
}
 @Transactional(value = "multiTransactionManager", rollbackFor = Exception.class)
    public void test(){
        Person person3 = new Person();
        person3.setName("張三3");
        person3Dao.insert(person3);

        Person person = new Person();
        person.setName("張三");
        personDao.insert(person);
        int a = 1/0;
}

在這種情況下,確實是保證不了兩個事務的完整性。

此路不通。


aop + aspect


我們可以定義一個切面,攔截一個自定義的註解,然後手動的開啟各個事務,在try裡執行目標方法,如果沒有異常,就commit各個事務,否則在catch裡面rollback各個事務。

定義註解

import org.springframework.core.annotation.AliasFor;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;

import java.lang.annotation.*;

@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface MultiTransaction {
    
    // 這裡也可以仿照@Transactional加上這些引數
    
//    @AliasFor("transactionManager")
//    String value() default "";
//    
//    @AliasFor("value")
//    String transactionManager() default "";
//    
//    Propagation propagation() default Propagation.REQUIRED;
//    
//    Isolation isolation() default Isolation.DEFAULT;
//    
//    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
//    
//    boolean readOnly() default false;
}

定義切面

攔截@MultiTransaction方法。

@Aspect
@Component
public class TransactionAspect {

    @Resource
    private DataSourceTransactionManager transactionManager;
    @Resource
    private DataSourceTransactionManager mysqlTransactionManager;

    /**
     * 犧牲了@Transactional的事務傳播與隔離的豐富性 變成了預設級別
     * @param proceedingJoinPoint
     * @return
     */
    @Around("@annotation(MultiTransaction)")
    public Object multiTransaction(ProceedingJoinPoint proceedingJoinPoint) {
        TransactionStatus neo4jTransactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());
        TransactionStatus transactionStatus = mysqlTransactionManager.getTransaction(new DefaultTransactionDefinition());

        try {
            Object obj = proceedingJoinPoint.proceed();
            mysqlTransactionManager.commit(transactionStatus);
            transactionManager.commit(neo4jTransactionStatus);            
            return obj;
        } catch (Throwable throwable) {
            mysqlTransactionManager.rollback(transactionStatus);
            transactionManager.rollback(neo4jTransactionStatus);
            System.err.println("multiTransaction fail:"+ throwable);
            throw new RuntimeException(throwable);
        }
    }
}

transactionManager.getTransaction(new DefaultTransactionDefinition())內部在判斷事務隔離傳播等後,會開啟一個事務doBegin()(視當前事務傳播與具體情況)。
這種情況犧牲了@Transactional的事務傳播與隔離的豐富性,變成了預設級別。
當然也可以在註解當中加上相就的引數,將事務的各種屬性傳進來,再通過DefaultTransactionDefinition設定。

DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        definition.setReadOnly(true);

不過事務非預設級別的情況沒有經過測試。不清楚具體表現。


另外要特別注意兩個事務管理器開啟和完成的順序,先開啟的後結束。不然聯合事務就不會生效,而且會產生異常。

先開啟後結束,這是一個典型的棧的應用場景,所以改造一下,用棧來儲存事務管理器,先進後出。
這樣就不用擔心事務管理器順序出錯。

@Aspect
@Component
public class TransactionAspect {

    @Resource
    private DataSourceTransactionManager transactionManager;
    @Resource
    private DataSourceTransactionManager mysqlTransactionManager;

    /**
     * 犧牲了@Transactional的事務傳播與隔離的豐富性 變成了預設級別
     * @param proceedingJoinPoint
     * @return
     */
    @Around("@annotation(MultiTransaction)")
    public Object multiTransaction(ProceedingJoinPoint proceedingJoinPoint) {
        Stack<DataSourceTransactionManager> dtmStack = new Stack<>();
        Stack<TransactionStatus> tsStack = new Stack<>();

        TransactionStatus neo4jTransactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());
        dtmStack.push(transactionManager);
        tsStack.push(neo4jTransactionStatus);
        TransactionStatus transactionStatus = mysqlTransactionManager.getTransaction(new DefaultTransactionDefinition());
        dtmStack.push(mysqlTransactionManager);
        tsStack.push(transactionStatus);

        try {
            Object obj = proceedingJoinPoint.proceed();
            while (!dtmStack.isEmpty()) {
                dtmStack.pop().commit(tsStack.pop());
            }
            return obj;
        } catch (Throwable throwable) {
            while (!dtmStack.isEmpty()) {
                dtmStack.pop().rollback(tsStack.pop());
            }
            throw new RuntimeException(throwable);
        }
    }
}

XA(JTA) + atomikos

這是前面提到的spring-data官方推薦的方案。

Java事務API(JTA:Java Transaction API)和它的同胞Java事務服務(JTS:Java Transaction Service),為J2EE平臺提供了分散式事務服務(distributed transaction)的能力。 某種程度上,可以認為JTA規範是XA規範的Java版,其把XA規範中規定的DTP模型互動介面抽象成Java介面中的方法,並規定每個方法要實現什麼樣的功能。

在DTP模型中,規定了模型的五個組成元素:應用程式(Application)、資源管理器(Resource Manager)、事務管理器(Transaction Manager)、通訊資源管理器(Communication Resource Manager)、 通訊協定(Communication Protocol)。

這裡主要關注兩個模組事務管理器(Transaction Manager簡稱TM)、通訊資源管理器(Communication Resource Manager簡稱RM)

TM供應商:

實現UserTransaction、TransactionManager、Transaction、TransactionSynchronizationRegistry、Synchronization、Xid介面,
通過與XAResource介面互動來實現分散式事務。此外,TM廠商如果要支援跨應用的分散式事務,那麼還要實現JTS規範定義的介面。

常見的TM提供者包括我們前面提到的application server,
包括:jboss、ejb server、weblogic等,以及一些以第三方類庫形式提供事務管理器功能的jotm、Atomikos。
這裡使用atomikos。atomikos提供了基於JTA規範的XA分散式事務TM的實現。  

RM供應商:

XAResource介面需要由資源管理器者來實現,XAResource介面中定義了一些方法,這些方法將會被TM進行呼叫,如:

start方法:開啟事務分支

end方法:結束事務分支

prepare方法:準備提交

commit方法:提交

rollback方法:回滾

recover方法:列出所有處於PREPARED狀態的事務分支

一些RM提供者,可能也會提供自己的Xid介面的實現。 比如mysql提供了MysqlXADataSource


DruidX提供了DruidXADataSource,整合了一些常用的資料庫


Hikari中沒有實現類似於HikariXADataSource的功能,它使用的是各資料庫自定義的XA物件(MysqlXADataSource等)。而Neo4j並沒有實現這種本地資源管理器。

所以這裡使用兩個不同的mysql資料庫來演示XA + atomikos 實現多資料來源下原生的事務。

mysql1的組態檔MybatisDsMysql1Config裡面資料來源設定部份由

@Bean(name = "ds1")
    @DependsOn("druidXADataSource1")
    @Primary
    public DataSource ds1DataSource() {
        return new DruidDataSource();
    }

變成

@Bean(name = "ds1")
    @DependsOn("druidXADataSource1")
    @Primary
    public DataSource ds1DataSource(@Qualifier("druidXADataSource1") DruidXADataSource dataSource ) {
        AtomikosDataSourceBean xaDataSource=new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(dataSource);
        xaDataSource.setUniqueResourceName("ds1");
        return xaDataSource;
    }

    /**
     * 注入DruidXADataSource,Druid對JTA的支援,支援XA協定,採用兩階段事務的提交
     * @return
     */
    @Bean(value = "druidXADataSource1")
    @ConfigurationProperties(prefix = "spring.datasource.pri")
    public DruidXADataSource druidXADataSource1(){
        return new DruidXADataSource();
    }

原來是注入設定直接生成一個DruidXADataSource資料來源,現在多了一步將DruidXADataSource數注入到AtomikosDataSourceBean物件,將後者作為資料來源。而後者可以開啟二階段事務提交。

mysql2資料來源同理。

然後再設定JtaTransactionManagerConfig

@Configuration
@EnableTransactionManagement
public class JtaTransactionManagerConfig {

    @Bean
    public UserTransaction userTransaction(){
        UserTransactionImp userTransactionImp = new UserTransactionImp();
        return userTransactionImp;
    }

    @Bean
    public TransactionManager atomikosTransactionManager() {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        userTransactionManager.setForceShutdown(true);
        return userTransactionManager;
    }

    @Bean("xatx")
    public JtaTransactionManager transactionManager(UserTransaction userTransaction,
                                                         TransactionManager transactionManager) {
        return new JtaTransactionManager(userTransaction, transactionManager);
    }
}

此時在方法上加入註解@Transactional(value="xatx")即可開啟二階段事務提交。
同時@Transactional表示預設事務管理器。不想開啟二階段事務提交的時候,value填入相應的事務管理器即可。
如@Transactional(value="mysqlTransactionManager")表示該方法只管理mysql1資料庫的事務。

XA+Atomikos的缺點在於並行效率低,我沒有經過效能測試,但是可以從原始碼當中看到,其每個階段都使用了synchronized

此功能需要保證mysql當前連線使用者的mysql XA_RECOVER_ADMIN許可權。
如果沒有通過以下命令賦予許可權。

GRANT XA_RECOVER_ADMIN ON *.* TO root@'%' ;
flush privileges;

測試結果:異常情況下,兩條記錄都不插入
正常情況下,兩條記錄均插入

debug模式下,完整地事務過程紀錄檔如下:

點選檢視完整紀錄檔
[2023-06-14 11:57:41.802] WARN 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : Thanks for using Atomikos! Evaluate http://www.atomikos.com/Main/ExtremeTransactions for advanced features and professional support
or register at http://www.atomikos.com/Main/RegisterYourDownload to disable this message and receive FREE tips & advice
Thanks for using Atomikos! Evaluate http://www.atomikos.com/Main/ExtremeTransactions for advanced features and professional support
or register at http://www.atomikos.com/Main/RegisterYourDownload to disable this message and receive FREE tips & advice
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : USING: com.atomikos.icatch.default_max_wait_time_on_shutdown = 9223372036854775807
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : USING: com.atomikos.icatch.allow_subtransactions = true
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : USING: com.atomikos.icatch.recovery_delay = 10000
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : USING: com.atomikos.icatch.automatic_resource_registration = true
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : USING: com.atomikos.icatch.oltp_max_retries = 5
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : USING: com.atomikos.icatch.client_demarcation = false
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : USING: com.atomikos.icatch.threaded_2pc = false
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : USING: com.atomikos.icatch.serial_jta_transactions = true
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : USING: com.atomikos.icatch.log_base_dir = ./
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : USING: com.atomikos.icatch.rmi_export_class = none
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : USING: com.atomikos.icatch.max_actives = 50
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : USING: com.atomikos.icatch.checkpoint_interval = 500
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : USING: com.atomikos.icatch.enable_logging = true
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : USING: com.atomikos.icatch.log_base_name = tmlog
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : USING: com.atomikos.icatch.max_timeout = 300000
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : USING: com.atomikos.icatch.trust_client_tm = false
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : USING: java.naming.factory.initial = com.sun.jndi.rmi.registry.RegistryContextFactory
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : USING: com.atomikos.icatch.tm_unique_name = 192.168.124.20.tm
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : USING: com.atomikos.icatch.forget_orphaned_log_entries_delay = 86400000
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : USING: com.atomikos.icatch.oltp_retry_interval = 10000
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : USING: java.naming.provider.url = rmi://localhost:1099
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : USING: com.atomikos.icatch.force_shutdown_on_vm_exit = false
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : USING: com.atomikos.icatch.default_jta_timeout = 10000
[2023-06-14 11:57:41.804] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.provider.imp.AssemblerImp] : Using default (local) logging and recovery...
[2023-06-14 11:57:41.806] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.recovery.imp.FileSystemRepository] : baseDir ./
[2023-06-14 11:57:41.806] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.recovery.imp.FileSystemRepository] : baseName tmlog
[2023-06-14 11:57:41.806] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.recovery.imp.FileSystemRepository] : LogFileLock com.atomikos.persistence.imp.LogFileLock@73fdd3eb
[2023-06-14 11:57:41.818] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.datasource.xa.XATransactionalResource] : ds1: refreshed XAResource
[2023-06-14 11:57:41.827] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.datasource.xa.XATransactionalResource] : ds3: refreshed XAResource
[2023-06-14 11:57:41.833] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.imp.CompositeTransactionManagerImp] : createCompositeTransaction ( 10000 ): created new ROOT transaction with id 192.168.124.20.tm168671506182700001
[2023-06-14 11:57:41.836] DEBUG 16900 [http-nio-8657-exec-1] [org.mybatis.spring.SqlSessionUtils] : Creating a new SqlSession
[2023-06-14 11:57:41.838] DEBUG 16900 [http-nio-8657-exec-1] [org.mybatis.spring.SqlSessionUtils] : Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5ba0c88f]
[2023-06-14 11:57:41.841] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.jdbc.AbstractDataSourceBean] : AtomikosDataSoureBean 'ds3': getConnection()...
[2023-06-14 11:57:41.841] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.jdbc.AbstractDataSourceBean] : AtomikosDataSoureBean 'ds3': init...
[2023-06-14 11:57:41.846] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.jdbc.AtomikosConnectionProxy] : atomikos connection proxy for com.mysql.cj.jdbc.ConnectionImpl@2f3d51b4: calling getAutoCommit...
[2023-06-14 11:57:41.846] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.jdbc.AtomikosConnectionProxy] : atomikos connection proxy for com.mysql.cj.jdbc.ConnectionImpl@2f3d51b4: calling toString...
[2023-06-14 11:57:41.846] DEBUG 16900 [http-nio-8657-exec-1] [org.mybatis.spring.transaction.SpringManagedTransaction] : JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2f3d51b4] will be managed by Spring
[2023-06-14 11:57:41.847] DEBUG 16900 [http-nio-8657-exec-1] [com.nyp.dao.mapper3.Person3Dao.insert] : ==>  Preparing: insert into t_person(name) value(?) 
[2023-06-14 11:57:41.849] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.imp.CompositeTransactionImp] : addParticipant ( XAResourceTransaction: 3139322E3136382E3132342E32302E746D313638363731353036313832373030303031:3139322E3136382E3132342E32302E746D31 ) for transaction 192.168.124.20.tm168671506182700001
[2023-06-14 11:57:41.849] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.datasource.xa.XAResourceTransaction] : XAResource.start ( 3139322E3136382E3132342E32302E746D313638363731353036313832373030303031:3139322E3136382E3132342E32302E746D31 , XAResource.TMNOFLAGS ) on resource ds3 represented by XAResource instance com.mysql.cj.jdbc.MysqlXAConnection@4a7819e4
[2023-06-14 11:57:41.850] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.imp.CompositeTransactionImp] : registerSynchronization ( com.atomikos.jdbc.AtomikosConnectionProxy$JdbcRequeueSynchronization@bc4a5276 ) for transaction 192.168.124.20.tm168671506182700001
[2023-06-14 11:57:41.851] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.jdbc.AtomikosConnectionProxy] : atomikos connection proxy for com.mysql.cj.jdbc.ConnectionImpl@2f3d51b4: calling prepareStatement(insert into t_person(name) value(?))...
[2023-06-14 11:57:41.861] DEBUG 16900 [http-nio-8657-exec-1] [com.nyp.dao.mapper3.Person3Dao.insert] : ==> Parameters: 張三3(String)
[2023-06-14 11:57:41.862] DEBUG 16900 [http-nio-8657-exec-1] [com.nyp.dao.mapper3.Person3Dao.insert] : <==    Updates: 1
[2023-06-14 11:57:41.862] DEBUG 16900 [http-nio-8657-exec-1] [org.mybatis.spring.SqlSessionUtils] : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5ba0c88f]
[2023-06-14 11:57:41.863] DEBUG 16900 [http-nio-8657-exec-1] [org.mybatis.spring.SqlSessionUtils] : Creating a new SqlSession
[2023-06-14 11:57:41.863] DEBUG 16900 [http-nio-8657-exec-1] [org.mybatis.spring.SqlSessionUtils] : Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4d66d2a4]
[2023-06-14 11:57:41.863] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.jdbc.AbstractDataSourceBean] : AtomikosDataSoureBean 'ds1': getConnection()...
[2023-06-14 11:57:41.863] INFO 16900 [http-nio-8657-exec-1] [com.atomikos.jdbc.AbstractDataSourceBean] : AtomikosDataSoureBean 'ds1': init...
[2023-06-14 11:57:41.863] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.jdbc.AtomikosConnectionProxy] : atomikos connection proxy for com.mysql.cj.jdbc.ConnectionImpl@50bd3a33: calling getAutoCommit...
[2023-06-14 11:57:41.863] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.jdbc.AtomikosConnectionProxy] : atomikos connection proxy for com.mysql.cj.jdbc.ConnectionImpl@50bd3a33: calling toString...
[2023-06-14 11:57:41.863] DEBUG 16900 [http-nio-8657-exec-1] [org.mybatis.spring.transaction.SpringManagedTransaction] : JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@50bd3a33] will be managed by Spring
[2023-06-14 11:57:41.863] DEBUG 16900 [http-nio-8657-exec-1] [com.nyp.dao.mapper1.PersonDao.insert] : ==>  Preparing: insert into t_person(name) value(?) 
[2023-06-14 11:57:41.863] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.imp.CompositeTransactionImp] : addParticipant ( XAResourceTransaction: 3139322E3136382E3132342E32302E746D313638363731353036313832373030303031:3139322E3136382E3132342E32302E746D32 ) for transaction 192.168.124.20.tm168671506182700001
[2023-06-14 11:57:41.863] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.datasource.xa.XAResourceTransaction] : XAResource.start ( 3139322E3136382E3132342E32302E746D313638363731353036313832373030303031:3139322E3136382E3132342E32302E746D32 , XAResource.TMNOFLAGS ) on resource ds1 represented by XAResource instance com.mysql.cj.jdbc.MysqlXAConnection@30715028
[2023-06-14 11:57:41.864] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.imp.CompositeTransactionImp] : registerSynchronization ( com.atomikos.jdbc.AtomikosConnectionProxy$JdbcRequeueSynchronization@bc4a5276 ) for transaction 192.168.124.20.tm168671506182700001
[2023-06-14 11:57:41.864] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.jdbc.AtomikosConnectionProxy] : atomikos connection proxy for com.mysql.cj.jdbc.ConnectionImpl@50bd3a33: calling prepareStatement(insert into t_person(name) value(?))...
[2023-06-14 11:57:41.864] DEBUG 16900 [http-nio-8657-exec-1] [com.nyp.dao.mapper1.PersonDao.insert] : ==> Parameters: 張三(String)
[2023-06-14 11:57:41.865] DEBUG 16900 [http-nio-8657-exec-1] [com.nyp.dao.mapper1.PersonDao.insert] : <==    Updates: 1
[2023-06-14 11:57:41.865] DEBUG 16900 [http-nio-8657-exec-1] [org.mybatis.spring.SqlSessionUtils] : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4d66d2a4]
[2023-06-14 11:57:41.865] DEBUG 16900 [http-nio-8657-exec-1] [org.mybatis.spring.SqlSessionUtils] : Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5ba0c88f]
[2023-06-14 11:57:41.865] DEBUG 16900 [http-nio-8657-exec-1] [org.mybatis.spring.SqlSessionUtils] : Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5ba0c88f]
[2023-06-14 11:57:41.865] DEBUG 16900 [http-nio-8657-exec-1] [org.mybatis.spring.SqlSessionUtils] : Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4d66d2a4]
[2023-06-14 11:57:41.865] DEBUG 16900 [http-nio-8657-exec-1] [org.mybatis.spring.SqlSessionUtils] : Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4d66d2a4]
[2023-06-14 11:57:41.866] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.jdbc.AtomikosConnectionProxy] : atomikos connection proxy for com.mysql.cj.jdbc.ConnectionImpl@2f3d51b4: close()...
[2023-06-14 11:57:41.866] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.datasource.xa.XAResourceTransaction] : XAResource.end ( 3139322E3136382E3132342E32302E746D313638363731353036313832373030303031:3139322E3136382E3132342E32302E746D31 , XAResource.TMSUCCESS ) on resource ds3 represented by XAResource instance com.mysql.cj.jdbc.MysqlXAConnection@4a7819e4
[2023-06-14 11:57:41.867] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.jdbc.AtomikosConnectionProxy] : atomikos connection proxy for com.mysql.cj.jdbc.ConnectionImpl@50bd3a33: close()...
[2023-06-14 11:57:41.867] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.datasource.xa.XAResourceTransaction] : XAResource.end ( 3139322E3136382E3132342E32302E746D313638363731353036313832373030303031:3139322E3136382E3132342E32302E746D32 , XAResource.TMSUCCESS ) on resource ds1 represented by XAResource instance com.mysql.cj.jdbc.MysqlXAConnection@30715028
[2023-06-14 11:57:41.871] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.datasource.xa.XAResourceTransaction] : XAResource.rollback ( 3139322E3136382E3132342E32302E746D313638363731353036313832373030303031:3139322E3136382E3132342E32302E746D31 ) on resource ds3 represented by XAResource instance com.mysql.cj.jdbc.MysqlXAConnection@4a7819e4
[2023-06-14 11:57:41.872] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.datasource.xa.XAResourceTransaction] : XAResource.rollback ( 3139322E3136382E3132342E32302E746D313638363731353036313832373030303031:3139322E3136382E3132342E32302E746D32 ) on resource ds1 represented by XAResource instance com.mysql.cj.jdbc.MysqlXAConnection@30715028
[2023-06-14 11:57:41.875] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.imp.CompositeTransactionImp] : rollback() done of transaction 192.168.124.20.tm168671506182700001
[2023-06-14 11:57:41.875] DEBUG 16900 [http-nio-8657-exec-1] [com.atomikos.icatch.imp.CompositeTransactionImp] : rollback() done of transaction 192.168.124.20.tm168671506182700001
[2023-06-14 11:57:41.879] DEBUG 16900 [http-nio-8657-exec-1] [org.springframework.data.neo4j.web.support.OpenSessionInViewInterceptor] : Closed Neo4j OGM Session in OpenSessionInViewInterceptor
[2023-06-14 11:57:41.881] ERROR 16900 [http-nio-8657-exec-1] [org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet]] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause
java.lang.ArithmeticException: / by zero
	at com.nyp.controller.DyController.test(DyController.java:54)
	at com.nyp.controller.DyController$$FastClassBySpringCGLIB$$2df3816d.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:747)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
	at com.nyp.controller.DyController$$EnhancerBySpringCGLIB$$cfcd46ca.test(<generated>)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:209)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:877)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:783)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:866)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:748)
[2023-06-14 11:57:41.885] DEBUG 16900 [http-nio-8657-exec-1] [org.springframework.data.neo4j.web.support.OpenSessionInViewInterceptor] : Opening Neo4j OGM Session in OpenSessionInViewInterceptor
[2023-06-14 11:57:41.900] DEBUG 16900 [http-nio-8657-exec-1] [org.springframework.data.neo4j.web.support.OpenSessionInViewInterceptor] : Closed Neo4j OGM Session in OpenSessionInViewInterceptor

可以看到atomikos開啟事務,獲取代理資料庫連線,回滾事務的過程。

另外,atomikos會在根目錄下記錄紀錄檔,兩個檔案分別是tmlog.lk,tmlog.log,多個範例寫入相同的檔案會報異常。
關閉此紀錄檔可通過設定spring.jta.atomikos.properties.enable-logging=false


原始碼地址:
https://github.com/nyingping/dydatasource

參考:
http://www.tianshouzhi.com/api/tutorials/distributed_transaction/385