集成框架 - 多数据源配置
前言
由于现在基本都是以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 类根据读取到的配置信息创建数据源对象。
等我有空写