暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MyBatis多数据源及MyBatis+atomikos分布式事务

我们家Java 2021-08-24
422

点击上方蓝色我们家Java,选择“关注


本篇我们来讲解MyBatis的多数据实现,与SpringDataJPA多数据源实现方式一样,仍然采取分包策略。


MyBatis多数据源



还是先来配置application.yml文件:

spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8

  datasource:
    family: 
      driver-class-namecom.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/Family?useUnicode=true&characterEncoding=utf-8&useSSL=false
      username: root
      password: 123456

    family2:
          driver-class-namecom.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/Family2?useUnicode=true&characterEncoding=utf-8&useSSL=false
          username: root
          password: 123456


将FamilyDemoApplication中的@MapperScan注解注释掉。


我们要在config文件夹下添加两个数据源配置:

第一个数据源配置:

package com.javafamily.familydemo.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = "com.javafamily.familydemo.generator.db",
        sqlSessionTemplateRef = "familySqlSessionTemplate")

public class FamilyDataSourceConfig {

    @Bean(name = "familyDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.family")
    @Primary
    public DataSource familyDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "familySqlSessionFactory")
    @Primary
    public SqlSessionFactory primarySqlSessionFactory(
            @Qualifier("familyDataSource") DataSource dataSource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        //设置XML文件存放位置
        bean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:generator/db/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "familyTransactionManager")
    @Primary
    public DataSourceTransactionManager familyTransactionManager(
            @Qualifier("familyDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "familySqlSessionTemplate")
    @Primary
    public SqlSessionTemplate familySqlSessionTemplate(
            @Qualifier("familySqlSessionFactory") SqlSessionFactory sqlSessionFactory)
            throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

第二个数据源配置:

package com.javafamily.familydemo.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = "com.javafamily.familydemo.generator.db2",
        sqlSessionTemplateRef = "family2SqlSessionTemplate")

public class Family2DataSourceConfig {

    @Bean(name = "family2DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.family2")
    public DataSource family2DataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "family2SqlSessionFactory")
    public SqlSessionFactory family2SqlSessionFactory(
            @Qualifier("family2DataSource") DataSource dataSource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:generator/db2/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "family2TransactionManager")
    public DataSourceTransactionManager family2TransactionManager(
            @Qualifier("family2DataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "family2SqlSessionTemplate")
    public SqlSessionTemplate family2SqlSessionTemplate(
            @Qualifier("family2SqlSessionFactory") SqlSessionFactory sqlSessionFactory)
            throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

完成数据源的配置后,PetsServiceImpl进行改写,向两个数据库中添加数据。

@Resource
    protected Mapper dozerMapper;

    @Resource
    private PetsDao petsDao;

    @Resource
    private DoctorDao doctorDao;


    @Override
    public PetsVO savePets(PetsVO pets) {
        Pets petsPO = dozerMapper.map(pets, Pets.class);
        petsDao.insert(petsPO);

        Doctor doctor = new Doctor();
        doctor.setName("Java");
        doctor.setTitle("实习");
        doctorDao.insert(doctor);

        return pets;
    }

完成代码改写后,执行代码,并在postman中进行测试:

数据分别传入两个数据库中:


MyBatis+atomikos分布式事务

与之前讲解的分布式事务一样,在PetsServiceImpl.java中的savePets方法中添加@Transactional注解和异常代码。

 @Resource
    protected Mapper dozerMapper;

    @Resource
    private PetsDao petsDao;

    @Resource
    private DoctorDao doctorDao;


    @Override
    @Transactional
    public PetsVO savePets(PetsVO pets) {
        Pets petsPO = dozerMapper.map(pets, Pets.class);
        petsDao.insert(petsPO);

        Doctor doctor = new Doctor();
        doctor.setName("Java");
        doctor.setTitle("实习");
        doctorDao.insert(doctor);

        // 添加异常
        int num = 1 / 0;
        return pets;
    }

由于没有添加分布式事务,所以在执行代码报错后,第一个数据库不会被插入数据,第二个数据库的数据会正常插入。


maven依赖已经在之前讲解的分布式事务文章中导入了,这里不再重复导入。


改写application.yml,实现双数据源配置:
spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
  datasource:
    family:
      xaDataSourceClassName: com.mysql.cj.jdbc.MysqlXADataSource
      xaProperties:
        url: jdbc:mysql://localhost:3306/Family?useUnicode=true&characterEncoding=utf-8&useSSL=false
        user: root
        password: 123456
      exclusiveConnectionMode: true
      minPoolSize: 2
      maxPoolSize: 10
      testQuery: SELECT 1 from dual

    family2:
      xaDataSourceClassName: com.mysql.cj.jdbc.MysqlXADataSource
      xaProperties:
        url: jdbc:mysql://localhost:3306/Family2?useUnicode=true&characterEncoding=utf-8&useSSL=false
        user: root
        password123456
      exclusiveConnectionMode: true
      minPoolSize: 2
      maxPoolSize: 10
      testQuery: SELECT 1 from dual

更改两个数据源配置:

第一个数据源配置:

package com.javafamily.familydemo.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = "com.javafamily.familydemo.generator.db",
        sqlSessionTemplateRef = "familySqlSessionTemplate")

public class FamilyDataSourceConfig {

    @Bean(name = "familyDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.family")
    @Primary
    public DataSource familyDataSource() {
        return new AtomikosDataSourceBean();
    }

    @Bean(name = "familySqlSessionFactory")
    @Primary
    public SqlSessionFactory primarySqlSessionFactory(
            @Qualifier("familyDataSource") DataSource dataSource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        //设置XML文件存放位置
        bean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("com/javafamily/familydemo/generator/db/*.xml"));
        return bean.getObject();
    }

//    @Bean(name = "familyTransactionManager")
//    @Primary
//    public DataSourceTransactionManager familyTransactionManager(
//            @Qualifier("familyDataSource") DataSource dataSource) {
//        return new DataSourceTransactionManager(dataSource);
//    }

    @Bean(name = "familySqlSessionTemplate")
    @Primary
    public SqlSessionTemplate familySqlSessionTemplate(
            @Qualifier("familySqlSessionFactory") SqlSessionFactory sqlSessionFactory)
            throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

第二个数据源配置:

package com.javafamily.familydemo.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = "com.javafamily.familydemo.generator.db2",
        sqlSessionTemplateRef = "family2SqlSessionTemplate")

public class Family2DataSourceConfig {

    @Bean(name = "family2DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.family2")
    public DataSource family2DataSource() {
        return new AtomikosDataSourceBean();
    }

    @Bean(name = "family2SqlSessionFactory")
    public SqlSessionFactory family2SqlSessionFactory(
            @Qualifier("family2DataSource") DataSource dataSource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("com/javafamily/familydemo/generator/db2/*.xml"));
        return bean.getObject();
    }

//    @Bean(name = "family2TransactionManager")
//    public DataSourceTransactionManager family2TransactionManager(
//            @Qualifier("family2DataSource") DataSource dataSource) {
//        return new DataSourceTransactionManager(dataSource);
//    }

    @Bean(name = "family2SqlSessionTemplate")
    public SqlSessionTemplate family2SqlSessionTemplate(
            @Qualifier("family2SqlSessionFactory") SqlSessionFactory sqlSessionFactory)
            throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

自动生成的代码,分别存放于db和db2两个文件夹:

由于更改了XML文件的存放位置所以移动文件目录后启动项目,如果发现target目录下没有加载到.xml文件时,需要在pom.xml下的<bulid></build>中加入以下配置:

<resources>
    <resource>
       <directory>src/main/java</directory>
       <includes>
          <include>**/*.xml</include>
       </includes>
    </resource>
 </resources>

在config文件夹中添加统一事务管理器:

package com.javafamily.familydemo.config;

import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.jta.JtaTransactionManager;

import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;

@Configuration
@EnableTransactionManagement
public class XATransactionManagerConfig {

    // User事务
    @Bean(name = "userTransaction")
    public UserTransaction userTransaction() throws Throwable {
        UserTransactionImp userTransactionImp = new UserTransactionImp();
        userTransactionImp.setTransactionTimeout(10000);
        return userTransactionImp;
    }

    // 分布式事务
    @Bean
    public TransactionManager atomikosTransactionManager() throws Throwable {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        userTransactionManager.setForceShutdown(false);
        return userTransactionManager;
    }

    // 事务管理器
    @Bean(name = "transactionManager")
    @DependsOn({"userTransaction""atomikosTransactionManager"})
    public PlatformTransactionManager transactionManager() throws Throwable {
        return new JtaTransactionManager(userTransaction(), atomikosTransactionManager());
    }

}

执行代码,向两个数据库中添加数据后,postman测试:

再查看数据库:


两个数据库均没有数据被插入,至此分布式事务完成。

点击下方阅读原文,查看上一篇

文章转载自我们家Java,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论