mysql和neo4j集成多數據源和事務

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


話說項目中需要用到圖數據庫,我們選用了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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章