spring boot+mybatis+atomikos實現多數據源分布式事務


1、項目上遇到的問題

  最近在做一個項目,需要同時用到oracle和mysql兩個數據庫,那么問題就來了,怎么實現多數據源呢?數據源之間是怎么切換呢?多數據源事務怎么控制呢?

  以下demo都是基於springboot。

2、其實實現多數據源還是很簡單的,主要是以下步驟

  1)配置application.yml,把之前的單數據源配置成多個

  2)手動配置每個數據源,包含sqlsessionfactory , transactionmanager,datasource, sqlsessiontemplate

3、那數據源怎么切換呢?

  我采用的分包的方法來實現的,找過網上很多資料,有通過注解切換的,有興趣的可以網上找找。

4、那多數據源事務怎么控制?

  因為是多數據源,所以是跨庫操作,單數據源不能保證兩個事務都回滾,這里可以說是分布式事務。

  關於分布式事務,XA ----->> 分布式事務協議 ,這里暫不多說

開始寫代碼吧

application.yml

spring:
  datasource:
    druid:
      m1:  #數據源1
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false&serverTimezone=UTC
        username: root
        password: 123456
        #初始化時建立物理連接的個數
        initialSize: 1
        #池中最大連接數
        maxActive: 20
        #最小空閑連接
        minIdle: 1
        #獲取連接時最大等待時間,單位毫秒
        maxWait: 60000
        #有兩個含義:
        #1) Destroy線程會檢測連接的間隔時間,如果連接空閑時間大於等於minEvictableIdleTimeMillis則關閉物理連接。
        #2) testWhileIdle的判斷依據,詳細看testWhileIdle屬性的說明
        timeBetweenEvictionRunsMillis: 60000
        #連接保持空閑而不被驅逐的最小時間,單位是毫秒
        minEvictableIdleTimeMillis: 300000
        #使用該SQL語句檢查鏈接是否可用。如果validationQuery=null,testOnBorrow、testOnReturn、testWhileIdle都不會起作用。
        validationQuery: SELECT 1 FROM DUAL
        #建議配置為true,不影響性能,並且保證安全性。申請連接的時候檢測,如果空閑時間大於timeBetweenEvictionRunsMillis,執行validationQuery檢測連接是否有效。
        testWhileIdle: true
        #申請連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能。
        testOnBorrow: false
        #歸還連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能。
        testOnReturn: false
        # 配置監控統計攔截的filters,去掉后監控界面sql無法統計,'wall'用於防火牆
        filters: stat,wall
        # 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
        #connectionProperties.druid.stat.mergeSql: true
        #connectionProperties.druid.stat.slowSqlMillis: 5000
        # 合並多個DruidDataSource的監控數據
        #useGlobalDataSourceStat: true
        #default-auto-commit: true 默認
        #default-auto-commit: false
      m2: #數據源2
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false&serverTimezone=UTC
        username: root
        password: 123456
        #初始化時建立物理連接的個數
        initialSize: 1
        #池中最大連接數
        maxActive: 20
        #最小空閑連接
        minIdle: 1
        #獲取連接時最大等待時間,單位毫秒
        maxWait: 60000
        #有兩個含義:
        #1) Destroy線程會檢測連接的間隔時間,如果連接空閑時間大於等於minEvictableIdleTimeMillis則關閉物理連接。
        #2) testWhileIdle的判斷依據,詳細看testWhileIdle屬性的說明
        timeBetweenEvictionRunsMillis: 60000
        #連接保持空閑而不被驅逐的最小時間,單位是毫秒
        minEvictableIdleTimeMillis: 300000
        #使用該SQL語句檢查鏈接是否可用。如果validationQuery=null,testOnBorrow、testOnReturn、testWhileIdle都不會起作用。
        validationQuery: SELECT 1 FROM DUAL
        #建議配置為true,不影響性能,並且保證安全性。申請連接的時候檢測,如果空閑時間大於timeBetweenEvictionRunsMillis,執行validationQuery檢測連接是否有效。
        testWhileIdle: true
        #申請連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能。
        testOnBorrow: false
        #歸還連接時執行validationQuery檢測連接是否有效,做了這個配置會降低性能。
        testOnReturn: false
        # 配置監控統計攔截的filters,去掉后監控界面sql無法統計,'wall'用於防火牆
        filters: stat,wall
        # 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
        #connectionProperties.druid.stat.mergeSql: true
        #connectionProperties.druid.stat.slowSqlMillis: 5000
        # 合並多個DruidDataSource的監控數據
        #useGlobalDataSourceStat: true
        #default-auto-commit: true 默認

m1  數據庫1

m2  數據庫2

pom

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--狀態監控-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- spring web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.vesion}</version>
        </dependency>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.15.0-GA</version>
        </dependency>
        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.spring.boot.starter.vesion}</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>${mybatis.vesion}</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>${mybatis.spring.vesion}</version>
        </dependency>
        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- mybatis 分頁 -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.1.2</version>
        </dependency>
        <!-- swagger2 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>${swagger.vesion}</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>${swagger.vesion}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.17</version>
        </dependency>

        <!-- XA協議  支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jta-atomikos</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.typesafe.akka/akka-actor -->
        <dependency>
            <groupId>com.typesafe.akka</groupId>
            <artifactId>akka-actor_2.13</artifactId>
            <version>2.6.3</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

數據庫1的配置:

@Component
@ConfigurationProperties(prefix = "spring.datasource.druid.m1")
public class M1DataSourceProperties {
    private String driverClassName;
 
    private String url;
 
    private String username;
 
    private String password;
 
    private Integer initialSize;

    private Integer maxActive;

    private Integer minIdle;
    private Integer maxWait;
    private Integer timeBetweenEvictionRunsMillis;

    private Integer minEvictableIdleTimeMillis;

    private String validationQuery;
    private Boolean testWhileIdle;
    private Boolean testOnBorrow;
    private Boolean testOnReturn;

    private String filters;
}

數據庫1的datasource配置:

import com.alibaba.druid.pool.xa.DruidXADataSource;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.github.pagehelper.PageInterceptor;
import com.test.maven.user.common.BeanUtils;
import org.apache.ibatis.plugin.Interceptor;
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.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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.transaction.jta.JtaTransactionManager;

import javax.sql.DataSource;
import javax.transaction.UserTransaction;
import java.util.Properties;

@Configuration
@MapperScan(basePackages = M1DataSourceConfig.PACKAGE, sqlSessionFactoryRef = "m1SqlSessionFactory",sqlSessionTemplateRef = "m1SqlSessionTemplate")
public class M1DataSourceConfig {


    static final String PACKAGE = "com.test.maven.user.dao.m1";
    static final String MAPPER_LOCATION = "classpath:mapper/m1/*.xml";

    @Autowired
    private M1DataSourceProperties m1DataSourceProperties;


    @Bean(name = "m1DataSource")
    @Primary
    public DataSource m1DataSource() {
        DruidXADataSource datasource = new DruidXADataSource();
        BeanUtils.copyProperties(m1DataSourceProperties,datasource);
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(datasource);
        xaDataSource.setUniqueResourceName("m1DataSource");
        System.out.println("數據源1初始化完成================");
        return xaDataSource;
    }

    @Bean(name = "transactionManager")
    public JtaTransactionManager transactionManager() {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        UserTransaction userTransaction = new UserTransactionImp();
        return new JtaTransactionManager(userTransaction, userTransactionManager);
    }


    @Bean(name = "m1SqlSessionFactory")
    @Primary
    public SqlSessionFactory m1SqlSessionFactory(@Qualifier("m1DataSource") DataSource m1DataSource)
            throws Exception {
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(m1DataSource);
        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources(M1DataSourceConfig.MAPPER_LOCATION));

        //分頁插件
        Interceptor interceptor = new PageInterceptor();
        Properties properties = new Properties();
        //數據庫
        properties.setProperty("helperDialect", "mysql");
        //是否將參數offset作為PageNum使用
        properties.setProperty("offsetAsPageNum", "true");
        //是否進行count查詢
        properties.setProperty("rowBoundsWithCount", "true");
        //是否分頁合理化
        properties.setProperty("reasonable", "false");
        interceptor.setProperties(properties);
        sessionFactory.setPlugins(new Interceptor[] {interceptor});
        return sessionFactory.getObject();
    }

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

數據庫2的屬性配置:

@Component
@ConfigurationProperties(prefix = "spring.datasource.druid.m2")
public class M2DataSourceProperties {
    private String driverClassName;
 
    private String url;
 
    private String username;
 
    private String password;
 
    private Integer initialSize;

    private Integer maxActive;

    private Integer minIdle;
    private Integer maxWait;
    private Integer timeBetweenEvictionRunsMillis;

    private Integer minEvictableIdleTimeMillis;

    private String validationQuery;
    private Boolean testWhileIdle;
    private Boolean testOnBorrow;
    private Boolean testOnReturn;

    private String filters;
}

數據庫2的DataSource配置:

import com.alibaba.druid.pool.xa.DruidXADataSource;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.github.pagehelper.PageInterceptor;
import com.test.maven.user.common.BeanUtils;
import org.apache.ibatis.plugin.Interceptor;
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.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;
import java.util.Properties;

@Configuration
@MapperScan(basePackages = M2DataSourceConfig.PACKAGE, sqlSessionFactoryRef = "m2SqlSessionFactory",sqlSessionTemplateRef = "m2SqlSessionTemplate")
public class M2DataSourceConfig {

    static final String PACKAGE = "com.test.maven.user.dao.m2";
    static final String MAPPER_LOCATION = "classpath:mapper/m2/*.xml";

    @Autowired
    private M2DataSourceProperties m2DataSourceProperties;


    @Bean(name = "m2DataSource")
    public DataSource m2DataSource() {
        DruidXADataSource datasource = new DruidXADataSource();
        BeanUtils.copyProperties(m2DataSourceProperties,datasource);
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(datasource);
        xaDataSource.setUniqueResourceName("m2DataSource");
        System.out.println("數據源2初始化完成================");
        return xaDataSource;
    }

    @Bean(name = "m2SqlSessionFactory")
    public SqlSessionFactory m2SqlSessionFactory(@Qualifier("m2DataSource") DataSource m2DataSource)
            throws Exception {
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(m2DataSource);
        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(M2DataSourceConfig.MAPPER_LOCATION));
        //分頁插件
        Interceptor interceptor = new PageInterceptor();
        Properties properties = new Properties();
        //數據庫
        properties.setProperty("helperDialect", "mysql");
        //是否將參數offset作為PageNum使用
        properties.setProperty("offsetAsPageNum", "true");
        //是否進行count查詢
        properties.setProperty("rowBoundsWithCount", "true");
        //是否分頁合理化
        properties.setProperty("reasonable", "false");
        interceptor.setProperties(properties);
        sessionFactory.setPlugins(new Interceptor[]{interceptor});
        return sessionFactory.getObject();
    }

    @Bean(name = "m2SqlSessionTemplate")
    public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("m2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

druid配置(狀態監控頁面):

@Configuration
public class DruidConfig {

    /**
     * 注冊一個StatViewServlet
     *
     * @return servlet registration bean
     */
    @Bean
    public ServletRegistrationBean druidStatViewServlet() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(
            new StatViewServlet(), "/druid/*");

        servletRegistrationBean.addInitParameter("loginUsername", "admin");
        servletRegistrationBean.addInitParameter("loginPassword", "123456");
        servletRegistrationBean.addInitParameter("resetEnable", "false");
        return servletRegistrationBean;
    }

    /**
     * 注冊一個:filterRegistrationBean
     *
     * @return filter registration bean
     */
    @Bean
    public FilterRegistrationBean druidStatFilter() {

        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(
            new WebStatFilter());

        // 添加過濾規則.
        filterRegistrationBean.addUrlPatterns("/*");

        // 添加不需要忽略的格式信息.
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }
}

項目結構:

 

這樣就基本完事了,可以實現數據源和事務控制了

上面m1的事務管理器只有一個,而在m2中是沒有配置的,因為是交給了atomikos,由它來管理事務,他實現了JTA/XA規范中的事務管理器,這里也是簡單的實現了功能.

以上是借鑒了網上資料和自己整理的,如有侵權,敬請諒解,謝謝。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM