SpringBoot2实现多数据源及分布式事务

论坛 期权论坛 脚本     
匿名网站用户   2020-12-21 09:34   495   0

SpringBoot2实现多数据源及分布式事务

本文记录基于SpringBoot2实现多数据源的配置以及多数据源情况下的分布式事务处理。

多数据源简介

所谓多数据源简单来说就是程序同时要对多个数据库进行增删改查。

分布式事务简介

分布式事务,举个例子来简单说明,假如程序配置了两个数据源db1和db2,在一个插入数据的方法中,先向db1中插入数据,再向db2中插入数据,该方法需要做事务处理,要求要么都插入,要么都不插入,这中间就涉及到,数据源2操作异常以后,要求数据源1和数据源2的事务都需要回滚。

环境简介

本文使用MySQL数据库,配置两个数据源

代码片段

Maven依赖:pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ictpaas</groupId>
    <artifactId>many-datasource-test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>many-datasource-test</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <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>

        <!--mysql JDBC 驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>

        <!--mybatis 与 spring boot 2.x 的整合包-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>

        <!--分布式事务-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jta-atomikos</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <!--MGB的Maven插件-->
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.7</version>
                <configuration>
                    <configurationFile>${basedir}/src/main/resources/mbg.xml</configurationFile>
                    <!--显示执行细节-->
                    <verbose>true</verbose>
                    <!--覆盖已有文件-->
                    <overwrite>true</overwrite>
                </configuration>
                <dependencies>
                    <!--为插件引入JDBC驱动-->
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>5.1.46</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

</project>

配置文件:application.properties

spring.datasource.first.url=jdbc:mysql://192.168.132.137:3306/db_one?useUnicode=true&characterEncoding=utf8
spring.datasource.first.username=root
spring.datasource.first.password=root

spring.datasource.second.url=jdbc:mysql://192.168.132.138:3306/db_two?useUnicode=true&characterEncoding=utf8
spring.datasource.second.username=root
spring.datasource.second.password=root

数据源1配置读取类:FirstDBConfig.java

package com.ictpaas.manydatasourcetest.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "spring.datasource.first")
public class FirstDBConfig {

    private String url;
    private String username;
    private String password;

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}

数据源2配置读取类:SecondDBConfig.java

package com.ictpaas.manydatasourcetest.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("spring.datasource.second")
public class SecondDBConfig {

    private String url;
    private String username;
    private String password;

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}

配置数据源1:FirstDataSourceConfig.java

package com.ictpaas.manydatasourcetest.config;

import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
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.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.ictpaas.manydatasourcetest.mapper.first", sqlSessionFactoryRef = "firstSqlSessionFactory")
public class FirstDataSourceConfig {

    @Primary
    @Bean(name = "firstDataSource")
    public DataSource dataSource(FirstDBConfig firstDBConfig) {
        MysqlXADataSource mysqlXADataSource = new MysqlXADataSource();
        mysqlXADataSource.setUrl(firstDBConfig.getUrl());
        mysqlXADataSource.setUser(firstDBConfig.getUsername());
        mysqlXADataSource.setPassword(firstDBConfig.getPassword());
        mysqlXADataSource.setPinGlobalTxToPhysicalConnection(true);

        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setXaDataSource(mysqlXADataSource);
        atomikosDataSourceBean.setUniqueResourceName("firstDataSource");

        return atomikosDataSourceBean;
    }

    @Primary
    @Bean(name = "firstSqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("firstDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);
        sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mybatis/mapper/first/*.xml"));
        return sessionFactoryBean.getObject();
    }

    @Primary
    @Bean(name = "firstSqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("firstSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}

配置数据源2:SecondDataSourceConfig.java

package com.ictpaas.manydatasourcetest.config;

import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = "com.ictpaas.manydatasourcetest.mapper.second", sqlSessionFactoryRef = "secondSqlSessionFactory")
public class SecondDataSourceConfig {

    @Bean(name = "secondDataSource")
    public DataSource dataSource(SecondDBConfig secondDBConfig) {
        MysqlXADataSource mysqlXADataSource = new MysqlXADataSource();
        mysqlXADataSource.setUrl(secondDBConfig.getUrl());
        mysqlXADataSource.setUser(secondDBConfig.getUsername());
        mysqlXADataSource.setPassword(secondDBConfig.getPassword());
        mysqlXADataSource.setPinGlobalTxToPhysicalConnection(true);

        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setXaDataSource(mysqlXADataSource);
        atomikosDataSourceBean.setUniqueResourceName("secondDataSource");

        return atomikosDataSourceBean;
    }

    @Bean(name = "secondSqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("secondDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);
        sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mybatis/mapper/second/*.xml"));
        return sessionFactoryBean.getObject();
    }

    @Bean(name = "secondSqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("secondSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}

实体类1:CdrOriginal.java

package com.ictpaas.manydatasourcetest.entity.first;

import java.util.Date;

public class CdrOriginal {
    private Long id;

    private String appid;

    private String globalId;

    private String cdr;

    private String producer;

    private String state;

    private String remark;

    private Date gmtCreate;

    private Date gmtModified;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getAppid() {
        return appid;
    }

    public void setAppid(String appid) {
        this.appid = appid;
    }

    public String getGlobalId() {
        return globalId;
    }

    public void setGlobalId(String globalId) {
        this.globalId = globalId;
    }

    public String getCdr() {
        return cdr;
    }

    public void setCdr(String cdr) {
        this.cdr = cdr;
    }

    public String getProducer() {
        return producer;
    }

    public void setProducer(String producer) {
        this.producer = producer;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    public Date getGmtCreate() {
        return gmtCreate;
    }

    public void setGmtCreate(Date gmtCreate) {
        this.gmtCreate = gmtCreate;
    }

    public Date getGmtModified() {
        return gmtModified;
    }

    public void setGmtModified(Date gmtModified) {
        this.gmtModified = gmtModified;
    }
    
}

实体类2:Channel.java

package com.ictpaas.manydatasourcetest.entity.second;

public class Channel {
    private Long channelId;

    private String channelName;

    private Integer sortNum;

    public Long getChannelId() {
        return channelId;
    }

    public void setChannelId(Long channelId) {
        this.channelId = channelId;
    }

    public String getChannelName() {
        return channelName;
    }

    public void setChannelName(String channelName) {
        this.channelName = channelName;
    }

    public Integer getSortNum() {
        return sortNum;
    }

    public void setSortNum(Integer sortNum) {
        this.sortNum = sortNum;
    }
    
}

业务层1:CdrOriginalService.java

package com.ictpaas.manydatasourcetest.service.first;

import com.ictpaas.manydatasourcetest.entity.first.CdrOriginal;
import com.ictpaas.manydatasourcetest.mapper.first.CdrOriginalMapper;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class CdrOriginalService {

    @Resource
    private CdrOriginalMapper cdrOriginalMapper;

    public void insert() {
        CdrOriginal cdrOriginal = new CdrOriginal();
        cdrOriginal.setAppid("appidtest001");
        cdrOriginal.setCdr("123123123123123");
        cdrOriginal.setGlobalId("globalidtest001");
        cdrOriginal.setProducer("TS");
        cdrOriginal.setState("ERROR");
        cdrOriginal.setRemark("测试数据");

        cdrOriginalMapper.insertSelective(cdrOriginal);
        throw new RuntimeException("调皮捣蛋");
    }
    
}

业务层2:ChannelService.java

package com.ictpaas.manydatasourcetest.service.second;

import com.ictpaas.manydatasourcetest.entity.second.Channel;
import com.ictpaas.manydatasourcetest.mapper.second.ChannelMapper;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class ChannelService {

    @Resource
    private ChannelMapper channelMapper;

    public void insert() {
        Channel channel = new Channel();
        channel.setChannelId((long)5);
        channel.setChannelName("排行");
        channel.setSortNum(5);

        channelMapper.insertSelective(channel);
    }
    
}

启动类:ManyDatasourceTestApplication.java

package com.ictpaas.manydatasourcetest;

import com.ictpaas.manydatasourcetest.config.FirstDBConfig;
import com.ictpaas.manydatasourcetest.config.SecondDBConfig;
import com.ictpaas.manydatasourcetest.service.first.CdrOriginalService;
import com.ictpaas.manydatasourcetest.service.second.ChannelService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@SpringBootApplication
@RestController
@EnableConfigurationProperties(value = {FirstDBConfig.class, SecondDBConfig.class})
public class ManyDatasourceTestApplication {

    @Resource
    private CdrOriginalMapper cdrOriginalMapper;
    @Resource
    private ChannelMapper channelMapper;

    public static void main(String[] args) {
        SpringApplication.run(ManyDatasourceTestApplication .class, args);
    }

    @Transactional
    @GetMapping("/mysql")
    public String first() {
        try {
            cdrOriginalService.insert();
            channelService.insert();
            return "SUCCESS";
        } catch (Exception e) {
            System.out.println("Transactional");
            throw new RuntimeException(e);
        }
    }

}
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:1136255
帖子:227251
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP