Spring Boot整合多个MyBatis数据源实战教程
在实际项目开发中,经常会遇到需要同时连接多个数据库的场景,例如读写分离架构(主库写、从库读)、多业务系统数据集成等。本文将详细讲解Spring Boot整合MyBatis实现多数据源的两种核心方案:静态多数据源 (固定数据源映射)和动态多数据源(运行时切换),并提供完整的代码示例和避坑指南。
一、前期准备
1.1 开发环境
-
开发工具:IntelliJ IDEA
-
构建工具:Maven 3.6+
-
框架版本:Spring Boot 2.7.x、MyBatis 3.5.x
-
数据库:MySQL 8.0(两个独立数据库,本文以db1和db2为例)
-
连接池:HikariCP(Spring Boot默认,性能最优)
1.2 项目依赖
在pom.xml中引入核心依赖,包含Spring Boot Web、MyBatis、MySQL驱动及动态数据源专用starter(如需动态切换):
xml
<dependencies>
<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.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
1.3 数据库准备
创建两个独立数据库并初始化测试表,以用户表为例:
sql
-- 数据库1:db1,存储主业务数据
CREATE DATABASE IF NOT EXISTS db1 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE db1;
CREATE TABLE IF NOT EXISTS t_user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
age INT NOT NULL,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 数据库2:db2,存储日志或附属业务数据
CREATE DATABASE IF NOT EXISTS db2 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE db2;
CREATE TABLE IF NOT EXISTS t_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
operation VARCHAR(100) NOT NULL,
operator VARCHAR(50) NOT NULL,
operate_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
二、静态多数据源实现(主从分离场景)
静态多数据源适用于固定数据源映射场景,例如不同业务模块对应不同数据库,或主库写、从库读的读写分离架构。核心思路是为每个数据源独立配置DataSource、SqlSessionFactory和事务管理器。
2.1 配置文件编写
在application.yml中配置两个数据源的连接信息,指定主数据源(@Primary标识):
yaml
spring:
datasource:
# 主数据源(db1)
primary:
url: jdbc:mysql://localhost:3306/db1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# Hikari连接池配置
hikari:
minimum-idle: 5
maximum-pool-size: 20
connection-timeout: 30000
# 从数据源(db2)
secondary:
url: jdbc:mysql://localhost:3306/db2?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
minimum-idle: 5
maximum-pool-size: 20
connection-timeout: 30000
# MyBatis全局配置
mybatis:
configuration:
map-underscore-to-camel-case: true # 下划线转驼峰
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志
mapper-locations: classpath:mapper/**/*.xml # Mapper XML文件路径
2.2 数据源配置类
创建数据源配置类,通过@ConfigurationProperties绑定配置文件中的数据源信息,为主数据源添加@Primary注解避免Bean冲突:
java
import com.zaxxer.hikari.HikariDataSource;
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 javax.sql.DataSource;
@Configuration
public class DataSourceConfig {
// 主数据源(db1)
@Primary
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
// 指定连接池类型为HikariCP
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
// 从数据源(db2)
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
}
2.3 MyBatis配置类
为每个数据源独立配置SqlSessionFactory和Mapper扫描路径,通过@MapperScan指定不同数据源对应的Mapper接口包:
2.3.1 主数据源MyBatis配置
java
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
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;
// 扫描主数据源的Mapper接口(com.example.mapper.primary包下)
@Configuration
@MapperScan(basePackages = "com.example.mapper.primary", sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryMyBatisConfig {
private final DataSource primaryDataSource;
// 注入主数据源
public PrimaryMyBatisConfig(@Qualifier("primaryDataSource") DataSource primaryDataSource) {
this.primaryDataSource = primaryDataSource;
}
// 主数据源的SqlSessionFactory
@Primary
@Bean(name = "primarySqlSessionFactory")
public SqlSessionFactory primarySqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(primaryDataSource);
// 配置主数据源的Mapper XML路径(可选,若全局配置已覆盖可省略)
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/primary/*.xml"));
return factoryBean.getObject();
}
}
2.3.2 从数据源MyBatis配置
java
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
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;
// 扫描从数据源的Mapper接口(com.example.mapper.secondary包下)
@Configuration
@MapperScan(basePackages = "com.example.mapper.secondary", sqlSessionFactoryRef = "secondarySqlSessionFactory")
public class SecondaryMyBatisConfig {
private final DataSource secondaryDataSource;
// 注入从数据源
public SecondaryMyBatisConfig(@Qualifier("secondaryDataSource") DataSource secondaryDataSource) {
this.secondaryDataSource = secondaryDataSource;
}
// 从数据源的SqlSessionFactory
@Bean(name = "secondarySqlSessionFactory")
public SqlSessionFactory secondarySqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(secondaryDataSource);
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/secondary/*.xml"));
return factoryBean.getObject();
}
}
2.4 事务管理器配置(可选)
若需要事务支持,需为每个数据源配置独立的事务管理器,使用@Transactional的value属性指定事务管理器:
java
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.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
@Configuration
public class TransactionManagerConfig {
// 主数据源事务管理器
@Primary
@Bean(name = "primaryTransactionManager")
public PlatformTransactionManager primaryTransactionManager(
@Qualifier("primaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
// 从数据源事务管理器
@Bean(name = "secondaryTransactionManager")
public PlatformTransactionManager secondaryTransactionManager(
@Qualifier("secondaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
2.5 业务层实现
按数据源划分Mapper接口和XML文件,编写Service层代码调用不同数据源的操作:
2.5.1 实体类
java
// 主数据源实体(db1.t_user)
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class User {
private Long id;
private String username;
private Integer age;
private LocalDateTime createTime;
}
// 从数据源实体(db2.t_log)
@Data
public class Log {
private Long id;
private String operation;
private String operator;
private LocalDateTime operateTime;
}
2.5.2 Mapper接口与XML
java
// 主数据源Mapper(com.example.mapper.primary.UserMapper)
import com.example.entity.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface UserMapper {
@Insert("INSERT INTO t_user(username, age) VALUES(#{username}, #{age})")
int insertUser(User user);
@Select("SELECT * FROM t_user")
List<User> listAllUsers();
}
// 从数据源Mapper(com.example.mapper.secondary.LogMapper)
import com.example.entity.Log;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface LogMapper {
@Insert("INSERT INTO t_log(operation, operator) VALUES(#{operation}, #{operator})")
int insertLog(Log log);
@Select("SELECT * FROM t_log")
List<Log> listAllLogs();
}
2.5.3 Service层
java
import com.example.entity.Log;
import com.example.entity.User;
import com.example.mapper.primary.UserMapper;
import com.example.mapper.secondary.LogMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@RequiredArgsConstructor
public class MultiDataSourceService {
private final UserMapper userMapper;
private final LogMapper logMapper;
// 操作主数据源,指定主事务管理器
@Transactional(value = "primaryTransactionManager")
public void addUser(User user) {
userMapper.insertUser(user);
// 模拟日志记录,调用从数据源
Log log = new Log();
log.setOperation("新增用户:" + user.getUsername());
log.setOperator("admin");
logMapper.insertLog(log);
}
// 查询主数据源数据
public List<User> getAllUsers() {
return userMapper.listAllUsers();
}
// 查询从数据源数据
public List<Log> getAllLogs() {
return logMapper.listAllLogs();
}
}
三、动态多数据源实现(运行时切换)
动态多数据源适用于运行时根据条件切换数据源的场景,例如按用户ID分库、按业务类型切换数据库等。核心思路是继承AbstractRoutingDataSource实现数据源路由,通过ThreadLocal存储当前线程的数据源标识。
3.1 核心组件开发
3.1.1 数据源上下文管理器
使用ThreadLocal存储当前线程的数据源标识,确保线程安全:
(注:文档部分内容可能由 AI 生成)