在微服务大行其道的今天,按理说不应该有多数据源这种问题(嗯,主从库算是一个多数据源的很常见的场景。),但是也没人规定不能这样做。 就算有人规定的,曾经被奉为圭臬的数据库三大范式现在被宽表冲得七零八落,在很多场景下,其实是鼓励建立冗余字段的。

话说项目中需要用到图数据库,我们选用了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。 机翻,将就看。

我自己测试了一下

@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

作者:|是奉壹呀|,原文链接: https://www.cnblogs.com/eryuan/p/17479664.html

文章推荐

一分钟学一个 Linux 命令 - pwd

【Lua】VSCode 搭建 Lua 开发环境

spring boot过滤器实现项目内接口过滤

Java代理之jdk动态代理+应用场景实战

Java GenericObjectPool 对象池化技术--SpringBoot sftp 连...

Oracle数据库 insert 插入数据 显示问号乱码的解决办法

Linux进程与线程的基本概念及区别

ReactQuery系列文章- 2. 数据转换

Java I/O(2):NIO中的Channel

Python趣味入门9:函数是你走过的套路,详解函数、调用、参...

记一次Tomcat卡死在 Deploying web application 步骤的问题

Docker 与 K8S学习笔记(二十三)—— Kubernetes集群搭建