Bootstrap

集成框架 - 多数据源配置

前言

由于现在基本都是以springBoot配置,所以我这边就按照springBoot配置多数据源,主要还是设计在分布式场景下,能用到的场景,其实也可以通过两边的服务调用,或者hsf,dubbo,以及grpc,或者springCloud相关openFign 或者说fign其实都可以进行两边软件的传输,也可以通过mq,比如说rabbitMQ这种轻量化mq进行传输,但是如果说一些记录方面的,或者A服务需要更高的权限,进行对B服务应用所对应的数据库,或者本地的副本数据库,做一些处理的时候,就要用到多数据源的情况,其实还有一种场景在于分库分表,其实在分表层面,我们大可使用shading这种组件进行编码配置,其实走到分库,项目就比较大了,那么mycat其实也是一种很好的选择,那么其实也就是分库使用,所以在工程方面,我们也还是需要对库选择,前提背景是这样,其实这种技术,只要有概念,有场景,剩下的就是落实这一块了。 我完整的起一个demo方便我们理解,和使用这方面。废话不多说,上代码即可。

正文

必要maven依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

配置多数据源

一、配置文件(application.yml)首先,我们需要在 application.yml 文件中配置多个数据源的连接信息。
spring:
  datasource:
    primary:
      url: jdbc:mysql://localhost:3306/primary_db?useSSL=false&serverTimezone=UTC
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
    secondary:
      url: jdbc:mysql://localhost:3306/secondary_db?useSSL=false&serverTimezone=UTC
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver

二、数据源配置类:

创建一个配置类,用于配置多个数据源及其相关的 SqlSessionFactory、SqlSessionTemplate 和 TransactionManager


import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
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.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;


@Configuration
public class DataSourceConfig {

    // 配置主数据源
    @Primary
    @Bean(name = "primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }


    // 为主数据源配置 SqlSessionFactory
    @Primary
    @Bean(name = "primarySqlSessionFactory")
    public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setLogImpl(StdOutImpl.class);
        factoryBean.setConfiguration(configuration);
        return factoryBean.getObject();
    }


    // 为主数据源配置 SqlSessionTemplate
    @Primary
    @Bean(name = "primarySqlSessionTemplate")
    public SqlSessionTemplate primarySqlSessionTemplate(@Qualifier("primarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }


    // 为主数据源配置事务管理器
    @Primary
    @Bean(name = "primaryTransactionManager")
    public DataSourceTransactionManager primaryTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }


    // 配置次数据源
    @Bean(name = "secondaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }


    // 为次数据源配置 SqlSessionFactory
    @Bean(name = "secondarySqlSessionFactory")
    public SqlSessionFactory secondarySqlSessionFactory(@Qualifier("secondaryDataSource") DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setLogImpl(StdOutImpl.class);
        factoryBean.setConfiguration(configuration);
        return factoryBean.getObject();
    }


    // 为次数据源配置 SqlSessionTemplate
    @Bean(name = "secondarySqlSessionTemplate")
    public SqlSessionTemplate secondarySqlSessionTemplate(@Qualifier("secondarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }


    // 为次数据源配置事务管理器
    @Bean(name = "secondaryTransactionManager")
    public DataSourceTransactionManager secondaryTransactionManager(@Qualifier("secondaryDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}
三、Mapper 扫描配置:

为不同的数据源配置不同的 Mapper 扫描,确保不同的 Mapper 接口使用不同的数据源。

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;


@Configuration
// 主数据源的 Mapper 扫描
@MapperScan(basePackages = "com.fawkes.material.primary.mapper", sqlSessionFactoryRef = "primarySqlSessionFactory",
        sqlSessionTemplateRef = "primarySqlSessionTemplate")
public class PrimaryMapperScanConfig {
}
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;


@Configuration
// 次数据源的 Mapper 扫描
@MapperScan(basePackages = "com.fawkes.material.secondary.mapper", sqlSessionFactoryRef = "secondarySqlSessionFactory",
        sqlSessionTemplateRef = "secondarySqlSessionTemplate")
public class SecondaryMapperScanConfig {
}

我比较喜欢省事,也可以在application启动类进行添加

package com.fawkes.material;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableDiscoveryClient
@SpringBootApplication
@MapperScan(basePackages = {"com.fawkes.material.primary.mapper","com.fawkes.material.secondary.mapper"})
@EnableScheduling
public class MaterialManageApplication {

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

四、使用示例:

假设你有以下两个 Mapper 接口:

主数据源 Mapper 接口:


import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.fawkes.material.entity.PrimaryEntity;
import org.apache.ibatis.annotations.Mapper;


public interface PrimaryMapper extends BaseMapper<PrimaryEntity> {
    // 这里可以添加自定义的 SQL 映射方法
}

次数据源 Mapper 接口:

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.fawkes.material.entity.SecondaryEntity;
import org.apache.ibatis.annotations.Mapper;


public interface SecondaryMapper extends BaseMapper<SecondaryEntity> {
    // 这里可以添加自定义的 SQL 映射方法
}
关于mybatis-plus 方面集成

在这里如果使用了mybatis-plus 就跟原来的使用一样,使用mybatis-plus 都是一样的


------------------------------- 接口 -------------------------------

public interface PrimaryService extends IService<Primary> {
    Object test();
}

------------------------------- 实现类 -------------------------------
@Service
public class PrimaryServiceImpl extends ServiceImpl<PrimaryMapper, Primary> implements PrimaryService {
    @Override
    public Object test() {
        LambdaQueryWrapper<Primary> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(Primary::getId,"1");

        Subcontract byId = getOne(wrapper);
        return byId;
    }
}

------------------------------- 分层 -------------------------------


public interface SecondaryService extends IService<Secondary> {
    Object test();
}

------------------------------- 实现类 -------------------------------
@Service
public class SecondaryServiceImpl extends ServiceImpl<SecondaryMapper, Secondary> implements SecondaryService {
    @Override
    public Object test() {
        LambdaQueryWrapper<Secondary> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(Primary::getId,"1");

        Subcontract byId = getOne(wrapper);
        return byId;
    }
}

关于事物管理器

对于事务管理,如果你需要在服务方法上使用 @Transactional 注解,要明确指定相应的事务管理器,例如:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


@Service
public class DataService {
    @Autowired
    private PrimaryMapper primaryMapper;
      @Autowired
    private SecondaryMapper primaryMapper;

    // 使用主数据源的事务管理器进行事务管理
    @Transactional(transactionManager = "primaryTransactionManager")
    // 使用子数据源的事务管理器进行事务管理
    // @Transactional(transactionManager = "secondaryTransactionManager")
    public void transactionalMethod() {
        // 此方法将使用主数据源的事务管理器进行事务管理
        primaryMapper.insert(...);
        // 此方法将使用子数据源的事务管理器进行事务管理
        // Secondary.insert(...);
        
    }
}

其实也可以不指定,当用主事物的时候,默认会走A的事务,如果用次事物,会自动判断走B事物。

但是这里其实有个问题,就是你对主事务和子事物都使用了,你既需要主事物会滚,也需要此事物回滚怎么办?

关于SpringBoot本地分布式事务

其实一个事务方法中既调用主数据源又调用次数据源,并确保事务都能回滚的问题其实在 Spring Boot 中,对于涉及多个数据源的事务操作,普通的 @Transactional 注解肯定上无法保证跨数据源的事务一致性的,因为它只管理单个数据源的事务。为了实现跨数据源的事务操作和回滚,就需要使用分布式事务管理。其实分布式事务,有人一开始肯定想到,用seta,也就是阿里巴巴的,但是你看我一个单应用,你觉得合理吗,需要这么重的主键,或者说还要去弄一个微服务架构去适配它呢

使用 Atomikos 前置要求 确保数据库支持 XA 协议, MySQL 需要使用 InnoDB 引擎。

一. 添加依赖:

在 pom.xml 中添加 Atomikos 依赖:


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
二 . 数据源和事务管理器配置:

在 Spring Boot 中配置数据源和事务管理器,使用 AtomikosDataSourceBean 来创建支持 XA 的数据源。

import com.atomikos.jdbc.AtomikosDataSourceBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;


@Configuration
public class JtaAtomikosDataSourceConfig {


    // 配置主数据源
    @Bean(initMethod = "init", destroyMethod = "close")
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public AtomikosDataSourceBean primaryDataSource() {
    	// AtomikosDataSourceBean 是 Atomikos 提供的支持 XA 协议的数据源
        AtomikosDataSourceBean dataSource = new AtomikosDataSourceBean();
        dataSource.setUniqueResourceName("primaryDataSource");
        // 设置数据库驱动类 配置了数据库的 XA 数据源类
        dataSource.setXaDataSourceClassName("com.mysql.cj.jdbc.MysqlXADataSource");
        // 设置连接池大小等参数 用于配置连接池大小和连接超时时间。
        dataSource.setMaxPoolSize(10);
        dataSource.setBorrowConnectionTimeout(60);
        return dataSource;
    }


    // 配置主数据源的事务管理器
    @Bean
    public DataSourceTransactionManager primaryTransactionManager() {
        return new DataSourceTransactionManager(primaryDataSource().getXaDataSource());
    }


    // 配置次数据源
    @Bean(initMethod = "init", destroyMethod = "close")
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public AtomikosDataSourceBean secondaryDataSource() {
        AtomikosDataSourceBean dataSource = new AtomikosDataSourceBean();
        dataSource.setUniqueResourceName("secondaryDataSource");
        dataSource.setXaDataSourceClassName("com.mysql.cj.jdbc.MysqlXADataSource");
        dataSource.setMaxPoolSize(10);
        dataSource.setBorrowConnectionTimeout(60);
        return dataSource;
    }


    // 配置次数据源的事务管理器
    @Bean
    public DataSourceTransactionManager secondaryTransactionManager() {
        return new DataSourceTransactionManager(secondaryDataSource().getXaDataSource());
    }
}
三: 使用

服务类中的事务方法:
在服务类中,使用 @Transactional 注解管理事务。


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


@Service
public class MultiDataSourceService {
    @Autowired
    private PrimaryMapper primaryMapper;
    @Autowired
    private SecondaryMapper secondaryMapper;

    // 使用分布式事务管理器处理事务
    @Transactional
    public void transactionalMethod() {
        try {
            // 操作主数据源
            primaryMapper.insert(new PrimaryEntity());

            // 操作次数据源
            secondaryMapper.insert(new SecondaryEntity());

            // 模拟异常,触发事务回滚
            throw new RuntimeException("Transaction rollback test");

        } catch (Exception e) {
            // 不加异常处理,事务会自动回滚
        }
    }
}
五:关于 Bitronix

使用 Bitronix 前置要求 确保数据库支持 XA 协议, MySQL 需要使用 InnoDB 引擎。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jta-bitronix</artifactId>
</dependency>

数据源和事务管理器配置: 使用 PoolingDataSource 来配置支持 XA 的数据源。

import bitronix.tm.resource.jdbc.PoolingDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;


@Configuration
public class JtaBitronixDataSourceConfig {


    // 配置主数据源
    @Bean(initMethod = "init", destroyMethod = "close")
    public PoolingDataSource primaryDataSource() {
    	// PoolingDataSource 是 Bitronix 提供的支持 XA 的数据源。
        PoolingDataSource dataSource = new PoolingDataSource();
        // 设置数据库的 XA 数据源类
        dataSource.setClassName("com.mysql.cj.jdbc.MysqlXADataSource");
        dataSource.setUniqueName("primaryDataSource");
        // 配置连接池大小。
        dataSource.setMaxPoolSize(5);
        dataSource.setAllowLocalTransactions(true);
        // 用于设置数据库连接的参数,如用户名、密码和 URL。
        dataSource.getDriverProperties().put("user", "root");
        dataSource.getDriverProperties().put("password", "root");
        dataSource.getDriverProperties().put("url", "jdbc:mysql://localhost:3306/primary_db?useSSL=false&serverTimezone=UTC");
        dataSource.init();
        return dataSource;
    }


    // 配置主数据源的事务管理器
    @Bean
    public DataSourceTransactionManager primaryTransactionManager() {
        //  DataSourceTransactionManager 使用 PoolingDataSource 来管理事务。
        return new DataSourceTransactionManager(primaryDataSource());
    }


    // 配置次数据源
    @Bean(initMethod = "init", destroyMethod = "close")
    public PoolingDataSource secondaryDataSource() {
        PoolingDataSource dataSource = new PoolingDataSource();
        dataSource.setClassName("com.mysql.cj.jdbc.MysqlXADataSource");
        dataSource.setUniqueName("secondaryDataSource");
        dataSource.setMaxPoolSize(5);
        dataSource.setAllowLocalTransactions(true);
        dataSource.getDriverProperties().put("user", "root");
        dataSource.getDriverProperties().put("password", "root");
        dataSource.getDriverProperties().put("url", "jdbc:mysql://localhost:3306/secondary_db?useSSL=false&serverTimezone=UTC");
        dataSource.init();
        return dataSource;
    }


    // 配置次数据源的事务管理器
    @Bean
    public DataSourceTransactionManager secondaryTransactionManager() {
        return new DataSourceTransactionManager(secondaryDataSource());
    }
}
六:结论

这两个都是基于 XA 协议,通过两阶段提交(2PC)或三阶段提交(3PC)协议来管理分布式事务,确保多个数据源的事务一致性,使用方式都一样。性能方面没有测过,不过我更喜欢atomikos 用的比较习惯,所以如果不是微服务,用atomikos就好了,如果是微服务,那么还是建议用seta,因为使用场景不一样罢了。

为什么配置多数据源的时候SpringBoot可以加载

其实加载一个数据源,我们在springBoot的配置文件里面配置就好了,如果是多个数据源呢,就会好奇它到底怎么做到的?

首先在 pom.xml 中添加相关的数据源依赖,Spring Boot 的自动配置机制开始发挥作用。它会根据你添加的依赖,引入相应的库和类路径,打个比方添加 spring-boot-starter-jdbc 会引入 spring-jdbc 和 HikariCP(默认的连接池)等相关库。

其实前面我写了自动化装配,所以,在application启动的时候,就回通过selectImport选择器进行加载,Spring Boot 会根据条件化配置(@Conditional 注解)加载相应的自动配置类。对于数据源,会加载 DataSourceAutoConfiguration 类。这个类包含了一系列的 @Bean 方法,是用于创建和配置数据源。

Spring Boot 会根据配置文件(application.properties 或 application.yml)中的信息,如 spring.datasource.type,来确定使用的数据源类型。如果未指定,通常会使用 HikariDataSource(因为 HikariCP 是 Spring Boot 的默认连接池)。

package org.springframework.boot.autoconfigure.jdbc;

在这里插入图片描述

在这里插入图片描述

EmbeddedDatabaseConfiguration // 用于配置嵌入式数据库的内部类
PooledDataSourceConfiguration // 用于配置池化数据源的内部类

使用 @ConfigurationProperties 注解的 DataSourceProperties 类会从配置文件中读取 spring.datasource 前缀的配置信息,包括 url、username、password、driver-class-name 等。

在这里插入图片描述
在这里插入图片描述
对于 HikariDataSource,会使用 DataSourceBuilder 类根据读取到的配置信息创建数据源对象。

等我有空写

;