Spring Boot的多數(shù)據(jù)源與分布式事務(wù)

由于平時(shí)項(xiàng)目里有用到多個(gè)數(shù)據(jù)源,之前采用AOP的方式切換數(shù)據(jù)源,卻發(fā)現(xiàn)事務(wù)無(wú)法生效。今天嘗試了下在Spring Boot下創(chuàng)建多個(gè)數(shù)據(jù)源,并實(shí)現(xiàn)分布式事務(wù),即多事務(wù)同步提交與回滾。

這里需要用到Atomikos,它是一種無(wú)需服務(wù)器支持的分布式事務(wù)組件。

接下來(lái)介紹如何搭建多數(shù)據(jù)源與分布式事務(wù):

  1. pom.xml新增依賴
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
  1. application.properties新增多數(shù)據(jù)源配置
#開(kāi)啟JTA支持
spring.jta.enabled=true
#數(shù)據(jù)源def
spring.datasource.def.xa-properties.url=jdbc:mysql://localhost:3306/yysoft?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&rewriteBatchedStatements=true
spring.datasource.def.xa-properties.username=root
spring.datasource.def.xa-properties.password=mysql
spring.datasource.def.xa-data-source-class-name=com.alibaba.druid.pool.xa.DruidXADataSource
#數(shù)據(jù)源唯一標(biāo)識(shí)
spring.datasource.def.unique-resource-name=defDataSource
#數(shù)據(jù)源opencart1
spring.datasource.opencart1.xa-properties.url=jdbc:mysql://localhost:3307/opencart1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&rewriteBatchedStatements=true
spring.datasource.opencart1.xa-properties.username=opencart1
spring.datasource.opencart1.xa-properties.password=uorejwrew
spring.datasource.opencart1.xa-data-source-class-name=com.alibaba.druid.pool.xa.DruidXADataSource
#數(shù)據(jù)源唯一標(biāo)識(shí)
spring.datasource.opencart1.unique-resource-name=opencart1DataSource
  1. Mapper要分成兩個(gè)目錄,xml不用,如圖所示
Mapper
xml
  1. 多數(shù)據(jù)源配置類
package com.yysoft.core.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 org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = "com.yysoft.core.dao.def", sqlSessionTemplateRef  = "defSqlSessionTemplate")//指定包使用的sqlSession
public class DefDSConfig {

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

    @Bean
    @Primary
    public SqlSessionFactory defSqlSessionFactory(@Qualifier("defDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:com/yysoft/core/dao/def/mapper/*Mapper.xml"));//掃描指定目錄的xml
        return bean.getObject();
    }

    @Bean
    @Primary
    public DataSourceTransactionManager defTransactionManager(@Qualifier("defDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    @Primary
    public SqlSessionTemplate defSqlSessionTemplate(@Qualifier("defSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}

這里主數(shù)據(jù)庫(kù)要用@Primary注解,或者會(huì)報(bào)錯(cuò)。

package com.yysoft.core.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 org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = "com.yysoft.core.dao.opencart1", sqlSessionTemplateRef  = "opencart1SqlSessionTemplate")//指定包使用的sqlSession
public class Opencart1DSConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.opencart1")
    public DataSource opencart1DataSource() {
        return new AtomikosDataSourceBean();
    }

    @Bean
    public SqlSessionFactory opencart1SqlSessionFactory(@Qualifier("opencart1DataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:com/yysoft/core/dao/opencart1/mapper/*Mapper.xml"));//掃描指定目錄的xml
        return bean.getObject();
    }

    @Bean
    public DataSourceTransactionManager opencart1TransactionManager(@Qualifier("opencart1DataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    public SqlSessionTemplate opencart1SqlSessionTemplate(@Qualifier("opencart1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}
  1. 事務(wù)管理器配置類
package com.yysoft.core.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.jta.JtaTransactionManager;

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

@Configuration
public class TransactionManagerConfig {

    @Bean
    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
    @DependsOn({ "userTransaction", "atomikosTransactionManager" })
    public PlatformTransactionManager transactionManager() throws Throwable {
        JtaTransactionManager manager = new JtaTransactionManager(userTransaction(), atomikosTransactionManager());
        return manager;
    }

}
  1. 在Service層使用
    通過(guò)4和5的配置,總共創(chuàng)建了兩個(gè)數(shù)據(jù)源,還有三個(gè)事務(wù)管理器,分別是主庫(kù)的事務(wù)管理器,次庫(kù)的事務(wù)管理器以及分布式事務(wù)管理器。通過(guò)@Transactional來(lái)控制Service類使用哪個(gè)事務(wù)管理器,如果是@Transactional,則為主庫(kù)事務(wù)(或@Transactional("defTransactionManager")),@Transactional("opencart1TransactionManager"),則為次庫(kù)事務(wù),@Transactional("transactionManager"),則為分布式事務(wù)。
package com.yysoft.core.service.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.yysoft.core.dao.def.SysMenuQueryMapper;
import com.yysoft.core.dao.def.SysUserMapper;
import com.yysoft.core.dao.opencart1.OcDownloadMapper;
import com.yysoft.core.model.OcDownload;
import com.yysoft.core.model.SysMenu;
import com.yysoft.core.model.SysUser;
import com.yysoft.core.model.SysUserExample;
import com.yysoft.core.web.model.MenuModel;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.yysoft.core.service.IUserService;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
@Transactional("transactionManager")//分布式事務(wù)
public class UserService implements IUserService {

    @Autowired
    private SysUserMapper sysUserMapper;

    @Autowired
    private OcDownloadMapper ocDownloadMapper;

    @Override
    public void removeUsers(String userIds) {
        String[] userIdArr = userIds.split(",");
        for (String userId : userIdArr) {
            SysUser user = sysUserMapper.selectByPrimaryKey(userId);
            user.setState("0");
            sysUserMapper.updateByPrimaryKey(user);//主庫(kù)
        }
        OcDownload ocDownload = new OcDownload();
        ocDownload.setFilename("111");
        ocDownload.setMask("000");
        ocDownload.setDateAdded(new Date());
        ocDownloadMapper.insertSelective(ocDownload);//次庫(kù)
        int i = 1/0;//制造異常
    }

}

當(dāng)執(zhí)行int i = 1/0;后,主庫(kù)和次庫(kù)的事務(wù)會(huì)同步回滾,從而保證了事務(wù)的一致性。

第一次寫(xiě)簡(jiǎn)書(shū),把自己今天配置的過(guò)程寫(xiě)了出來(lái),希望也能幫到一些人。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容