文章目录
-
- 1. 什么是多数据源
-
- 1.1 基本概念
- 1.2 传统单数据源 vs 多数据源
-
- 单数据源架构
- 多数据源架构
- 2. 为什么需要多数据源
-
- 2.1 业务场景需求
- 2.2 技术优势
- 3. 多数据源的实现方式
-
- 3.1 静态多数据源
- 3.2 动态多数据源
- 4. 环境准备
-
- 4.1 创建SpringBoot项目
-
- pom.xml依赖配置
- 4.2 准备测试数据库
-
- 创建两个测试数据库
- 5. 方法一:使用@Primary和@Qualifier注解
-
- 5.1 配置文件
-
- application.yml
- 5.2 数据源配置类
-
- DataSourceConfig.java
- 5.3 MyBatis配置类
-
- PrimaryMyBatisConfig.java
- SecondaryMyBatisConfig.java
- 5.4 实体类
-
- User.java
- Order.java
- 5.5 Mapper接口
-
- UserMapper.java
- OrderMapper.java
- 5.6 MyBatis XML映射文件
-
- UserMapper.xml
- OrderMapper.xml
- 5.7 Service层
-
- UserService.java
- OrderService.java
- 5.8 Controller层
-
- UserController.java
- OrderController.java
- 6. 方法二:使用@ConfigurationProperties
-
- 6.1 更简洁的配置方式
-
- DataSourceProperties.java
- application.yml(配置属性方式)
- EnhancedDataSourceConfig.java
- 7. 方法三:动态数据源切换
-
- 7.1 动态数据源核心类
-
- DynamicDataSource.java
- DataSourceContextHolder.java
- DataSource.java(注解)
- 7.2 AOP切面处理数据源切换
-
- DataSourceAspect.java
- 7.3 动态数据源配置
-
- DynamicDataSourceConfig.java
- 7.4 使用动态数据源的Service
-
- DynamicUserService.java
- DynamicOrderService.java
- 7.5 动态数据源控制器
-
- DynamicController.java
- 8. 方法四:使用MyBatis-Plus多数据源
-
- 8.1 添加MyBatis-Plus依赖
-
- pom.xml(添加MyBatis-Plus)
- 8.2 MyBatis-Plus配置
-
- application.yml(MyBatis-Plus多数据源配置)
- 8.3 MyBatis-Plus实体类
-
- User.java(MyBatis-Plus版本)
- Order.java(MyBatis-Plus版本)
- 8.4 MyBatis-Plus Mapper接口
-
- UserPlusMapper.java
- OrderPlusMapper.java
- 8.5 MyBatis-Plus Service层
-
- UserPlusService.java
- OrderPlusService.java
- 8.6 MyBatis-Plus控制器
-
- UserPlusController.java
- 9. 事务管理
-
- 9.1 单数据源事务
-
- 基本事务使用
- 9.2 多数据源事务
-
- ChainedTransactionManager(链式事务管理器)
- 多数据源事务服务
- 9.3 分布式事务
-
- JTA配置(Atomikos)
- application.yml(JTA配置)
- JTA数据源配置
- 10. 常见问题和解决方案
-
- 10.1 循环依赖问题
-
- 问题描述
- 解决方案
- 10.2 事务不生效问题
-
- 问题1:类内部调用
- 解决方案
- 问题2:异常类型不匹配
- 解决方案
- 10.3 数据源配置错误
-
- 问题:配置文件格式错误
- 解决方案
- 10.4 MyBatis映射问题
-
- 问题:SQL映射找不到
- 解决方案
- 10.5 连接池配置问题
-
- 监控和调优连接池
- 11. 最佳实践
-
- 11.1 配置规范
-
- 1. 数据源命名规范
- 2. 配置文件组织
- 11.2 包结构规范
- 11.3 性能优化
-
- 1. 连接池优化
- 2. 读写分离优化
- 11.4 监控和维护
-
- 1. 数据源监控
- 2. 健康检查
- 11.5 安全最佳实践
-
- 1. 密码加密
- 2. 连接参数安全
- 12. 总结
-
- 12.1 多数据源实现方式对比
- 12.2 选择建议
-
- 1. 简单业务场景
- 2. 复杂业务场景
- 3. 快速开发
- 4. 企业级应用
- 12.3 重要注意事项
- 12.4 学习路径建议
1. 什么是多数据源
1.1 基本概念
多数据源(Multiple DataSources)是指在一个SpringBoot应用中同时连接和使用多个数据库的技术。这些数据库可以是:
- 不同类型的数据库:MySQL、PostgreSQL、Oracle等
- 相同类型的不同实例:主库、从库、不同业务的数据库
- 不同环境的数据库:开发、测试、生产环境
1.2 传统单数据源 vs 多数据源
单数据源架构
SpringBoot应用 → 单个DataSource → 单个数据库
多数据源架构
SpringBoot应用 → DataSource1 → 数据库1(用户数据)→ DataSource2 → 数据库2(订单数据)→ DataSource3 → 数据库3(日志数据)
2. 为什么需要多数据源
2.1 业务场景需求
- 读写分离:主库写入,从库读取,提高性能
- 业务隔离:不同业务模块使用独立数据库
- 数据迁移:新老系统数据库并存
- 分布式架构:微服务架构中的数据分离
2.2 技术优势
- 性能优化:分散数据库压力
- 数据安全:重要数据隔离存储
- 扩展性好:易于水平扩展
- 故障隔离:一个数据库故障不影响其他业务
3. 多数据源的实现方式
3.1 静态多数据源
- 编译时确定:在代码中明确指定使用哪个数据源
- 配置简单:通过注解或配置类实现
- 性能较好:没有动态切换的开销
3.2 动态多数据源
- 运行时决定:根据业务逻辑动态选择数据源
- 灵活性高:可以根据条件动态切换
- 实现复杂:需要自定义路由逻辑
4. 环境准备
4.1 创建SpringBoot项目
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.7.8</version><relativePath/></parent><groupId>com.example</groupId><artifactId>multi-datasource-demo</artifactId><version>1.0.0</version><name>multi-datasource-demo</name><description>SpringBoot多数据源配置示例</description><properties><java.version>1.8</java.version></properties><dependencies><!-- SpringBoot Web Starter --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- SpringBoot JDBC Starter --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><!-- MyBatis Starter --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.3.0</version></dependency><!-- MySQL驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!-- 连接池 --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.16</version></dependency><!-- Lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!-- Test --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
4.2 准备测试数据库
创建两个测试数据库
-- 创建用户数据库
CREATE DATABASE user_db CHARACTER SET utf8mb4;-- 创建订单数据库
CREATE DATABASE order_db CHARACTER SET utf8mb4;-- 用户表
USE user_db;
CREATE TABLE user (id BIGINT PRIMARY KEY AUTO_INCREMENT,username VARCHAR(50) NOT NULL,email VARCHAR(100),age INT,create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);INSERT INTO user (username, email, age) VALUES
('张三', 'zhangsan@example.com', 25),
('李四', 'lisi@example.com', 30),
('王五', 'wangwu@example.com', 28);-- 订单表
USE order_db;
CREATE TABLE orders (id BIGINT PRIMARY KEY AUTO_INCREMENT,user_id BIGINT NOT NULL,product_name VARCHAR(100) NOT NULL,price DECIMAL(10,2),quantity INT,create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);INSERT INTO orders (user_id, product_name, price, quantity) VALUES
(1, 'iPhone 14', 8999.00, 1),
(2, 'MacBook Pro', 15999.00, 1),
(1, 'AirPods', 1999.00, 2);
5. 方法一:使用@Primary和@Qualifier注解
5.1 配置文件
application.yml
# SpringBoot多数据源配置
spring:# 主数据源配置(用户数据库)datasource:primary:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/user_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456type: com.alibaba.druid.pool.DruidDataSource# Druid连接池配置druid:initial-size: 5min-idle: 5max-active: 20max-wait: 60000time-between-eviction-runs-millis: 60000min-evictable-idle-time-millis: 300000validation-query: SELECT 1test-while-idle: truetest-on-borrow: falsetest-on-return: false# 从数据源配置(订单数据库)secondary:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456type: com.alibaba.druid.pool.DruidDataSourcedruid:initial-size: 5min-idle: 5max-active: 20max-wait: 60000time-between-eviction-runs-millis: 60000min-evictable-idle-time-millis: 300000validation-query: SELECT 1test-while-idle: truetest-on-borrow: falsetest-on-return: false# MyBatis配置
mybatis:mapper-locations: classpath:mapper/**/*.xmltype-aliases-package: com.example.entityconfiguration:map-underscore-to-camel-case: truelog-impl: org.apache.ibatis.logging.stdout.StdOutImpl# 日志配置
logging:level:com.example.mapper: debugorg.springframework.jdbc: debug
5.2 数据源配置类
DataSourceConfig.java
package com.example.config;import com.alibaba.druid.pool.DruidDataSource;
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.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.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;import javax.sql.DataSource;/*** 多数据源配置类* 使用@Primary和@Qualifier注解方式实现多数据源*/
@Configuration
public class DataSourceConfig {/*** 创建主数据源(用户数据库)* @Primary 注解表示这是主要的数据源,当有多个同类型Bean时优先使用此Bean* @ConfigurationProperties 自动绑定配置文件中的属性*/@Primary@Bean(name = "primaryDataSource")@ConfigurationProperties(prefix = "spring.datasource.primary")public DataSource primaryDataSource() {return new DruidDataSource();}/*** 创建从数据源(订单数据库)*/@Bean(name = "secondaryDataSource")@ConfigurationProperties(prefix = "spring.datasource.secondary")public DataSource secondaryDataSource() {return new DruidDataSource();}/*** 主数据源的JdbcTemplate*/@Primary@Bean(name = "primaryJdbcTemplate")public JdbcTemplate primaryJdbcTemplate(@Qualifier("primaryDataSource") DataSource dataSource) {return new JdbcTemplate(dataSource);}/*** 从数据源的JdbcTemplate*/@Bean(name = "secondaryJdbcTemplate")public JdbcTemplate secondaryJdbcTemplate(@Qualifier("secondaryDataSource") DataSource dataSource) {return new JdbcTemplate(dataSource);}/*** 主数据源的事务管理器*/@Primary@Bean(name = "primaryTransactionManager")public DataSourceTransactionManager primaryTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}/*** 从数据源的事务管理器*/@Bean(name = "secondaryTransactionManager")public DataSourceTransactionManager secondaryTransactionManager(@Qualifier("secondaryDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}
}
5.3 MyBatis配置类
PrimaryMyBatisConfig.java
package com.example.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.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;/*** 主数据源MyBatis配置* 扫描用户相关的Mapper接口*/
@Configuration
@MapperScan(basePackages = "com.example.mapper.user", sqlSessionTemplateRef = "primarySqlSessionTemplate")
public class PrimaryMyBatisConfig {/*** 主数据源的SqlSessionFactory*/@Primary@Bean(name = "primarySqlSessionFactory")public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(dataSource);// 设置MyBatis配置文件位置bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/user/*.xml"));// 设置实体类别名包路径bean.setTypeAliasesPackage("com.example.entity");return bean.getObject();}/*** 主数据源的SqlSessionTemplate*/@Primary@Bean(name = "primarySqlSessionTemplate")public SqlSessionTemplate primarySqlSessionTemplate(@Qualifier("primarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) {return new SqlSessionTemplate(sqlSessionFactory);}
}
SecondaryMyBatisConfig.java
package com.example.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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;import javax.sql.DataSource;/*** 从数据源MyBatis配置* 扫描订单相关的Mapper接口*/
@Configuration
@MapperScan(basePackages = "com.example.mapper.order", sqlSessionTemplateRef = "secondarySqlSessionTemplate")
public class SecondaryMyBatisConfig {/*** 从数据源的SqlSessionFactory*/@Bean(name = "secondarySqlSessionFactory")public SqlSessionFactory secondarySqlSessionFactory(@Qualifier("secondaryDataSource") DataSource dataSource) throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(dataSource);// 设置MyBatis配置文件位置bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/order/*.xml"));// 设置实体类别名包路径bean.setTypeAliasesPackage("com.example.entity");return bean.getObject();}/*** 从数据源的SqlSessionTemplate*/@Bean(name = "secondarySqlSessionTemplate")public SqlSessionTemplate secondarySqlSessionTemplate(@Qualifier("secondarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) {return new SqlSessionTemplate(sqlSessionFactory);}
}
5.4 实体类
User.java
package com.example.entity;import lombok.Data;
import java.time.LocalDateTime;/*** 用户实体类*/
@Data
public class User {private Long id;private String username;private String email;private Integer age;private LocalDateTime createTime;
}
Order.java
package com.example.entity;import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;/*** 订单实体类*/
@Data
public class Order {private Long id;private Long userId;private String productName;private BigDecimal price;private Integer quantity;private LocalDateTime createTime;
}
5.5 Mapper接口
UserMapper.java
package com.example.mapper.user;import com.example.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;import java.util.List;/*** 用户Mapper接口* 此接口会使用主数据源(用户数据库)*/
@Mapper
public interface UserMapper {/*** 查询所有用户*/List<User> findAll();/*** 根据ID查询用户*/User findById(@Param("id") Long id);/*** 根据用户名查询用户*/User findByUsername(@Param("username") String username);/*** 插入用户*/int insert(User user);/*** 更新用户*/int update(User user);/*** 删除用户*/int deleteById(@Param("id") Long id);
}
OrderMapper.java
package com.example.mapper.order;import com.example.entity.Order;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;import java.util.List;/*** 订单Mapper接口* 此接口会使用从数据源(订单数据库)*/
@Mapper