# Spring多数据源完全指南
## 第1章 多数据源概述
### 1.1 什么是多数据源
在复杂的企业应用中,经常需要同时连接多个数据库,这种情况下就需要使用多数据源(Multi-Datasource)技术。多数据源可以是不同类型的数据库(如MySQL、Oracle、PostgreSQL等),也可以是相同类型但不同实例的数据库。
### 1.2 多数据源的应用场景
1. **读写分离**:主数据库负责写操作,从数据库负责读操作,提高系统性能
2. **业务分库**:不同业务模块使用不同的数据库,降低耦合度
3. **数据分析**:同时从生产库和数据仓库读取数据
4. **系统集成**:需要同时操作多个遗留系统的数据库
### 1.3 Spring中实现多数据源的方式
1. **静态多数据源**:通过配置文件预定义多个数据源
2. **动态多数据源**:在运行时根据需求动态创建和切换数据源
3. **分布式事务**:使用XA事务管理器协调多个数据源的事务
## 第2章 静态多数据源配置
### 2.1 基于配置文件的多数据源
#### 2.1.1 配置文件(application.yml)
```yaml
spring:
datasource:
# 主数据源
primary:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_primary?useSSL=false&serverTimezone=UTC
username: root
password: root
# 从数据源
secondary:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_secondary?useSSL=false&serverTimezone=UTC
username: root
password: root
```
#### 2.1.2 数据源配置类
```java
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 javax.sql.DataSource;
@Configuration
public class DataSourceConfig {
// 主数据源配置
@Primary
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
// 从数据源配置
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
}
```
### 2.2 配置JdbcTemplate
```java
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
@Configuration
public class JdbcTemplateConfig {
// 主数据源的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);
}
}
```
### 2.3 配置MyBatis多数据源
```java
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.example.mapper.primary", sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryMyBatisConfig {
@Bean(name = "primarySqlSessionFactory")
@Primary
public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/primary/*.xml"));
return bean.getObject();
}
@Bean(name = "primarySqlSessionTemplate")
@Primary
public SqlSessionTemplate primarySqlSessionTemplate(@Qualifier("primarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
```
```java
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.example.mapper.secondary", sqlSessionFactoryRef = "secondarySqlSessionFactory")
public class SecondaryMyBatisConfig {
@Bean(name = "secondarySqlSessionFactory")
public SqlSessionFactory secondarySqlSessionFactory(@Qualifier("secondaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/secondary/*.xml"));
return bean.getObject();
}
@Bean(name = "secondarySqlSessionTemplate")
public SqlSessionTemplate secondarySqlSessionTemplate(@Qualifier("secondarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
```
## 第3章 动态创建数据源
### 3.1 数据源动态创建的核心思路
动态数据源是指在运行时根据业务需求创建和管理不同的数据源,而不是在配置文件中预先定义。实现动态数据源需要考虑以下几点:
1. **数据源缓存**:创建数据源池,避免频繁创建和销毁数据源
2. **数据源切换**:提供在不同数据源之间切换的机制
3. **数据源管理**:动态添加、删除、修改数据源
### 3.2 动态数据源实现代码
#### 3.2.1 数据源配置类
```java
import lombok.Data;
@Data
public class DynamicDataSourceConfig {
private String driverClassName;
private String url;
private String username;
private String password;
// 连接池配置
private int minIdle;
private int maxActive;
private long maxWait;
private long timeBetweenEvictionRunsMillis;
// 构造方法和getter/setter省略
}
```
#### 3.2.2 动态数据源管理类
```java
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class DynamicDataSourceManager {
// 存储所有数据源的Map,使用ConcurrentHashMap保证线程安全
private final Map<String, DataSource> dataSourceMap = new ConcurrentHashMap<>();
/**
* 创建数据源
*/
public DataSource createDataSource(DynamicDataSourceConfig config) {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDriverClassName(config.getDriverClassName());
hikariConfig.setJdbcUrl(config.getUrl());
hikariConfig.setUsername(config.getUsername());
hikariConfig.setPassword(config.getPassword());
// 配置连接池参数
hikariConfig.setMinimumIdle(config.getMinIdle() > 0 ? config.getMinIdle() : 5);
hikariConfig.setMaximumPoolSize(config.getMaxActive() > 0 ? config.getMaxActive() : 20);
hikariConfig.setMaxLifetime(1800000);
hikariConfig.setConnectionTimeout(config.getMaxWait() > 0 ? config.getMaxWait() : 30000);
hikariConfig.setIdleTimeout(config.getTimeBetweenEvictionRunsMillis() > 0 ? config.getTimeBetweenEvictionRunsMillis() : 600000);
return new HikariDataSource(hikariConfig);
}
/**
* 添加数据源
*/
public void addDataSource(String dataSourceKey, DynamicDataSourceConfig config) {
if (dataSourceMap.containsKey(dataSourceKey)) {
throw new RuntimeException("数据源" + dataSourceKey + "已存在");
}
DataSource dataSource = createDataSource(config);
dataSourceMap.put(dataSourceKey, dataSource);
}
/**
* 获取数据源
*/
public DataSource getDataSource(String dataSourceKey) {
DataSource dataSource = dataSourceMap.get(dataSourceKey);
if (dataSource == null) {
throw new RuntimeException("数据源" + dataSourceKey + "不存在");
}
return dataSource;
}
/**
* 删除数据源
*/
public void removeDataSource(String dataSourceKey) {
DataSource dataSource = dataSourceMap.remove(dataSourceKey);
if (dataSource != null && dataSource instanceof HikariDataSource) {
((HikariDataSource) dataSource).close();
}
}
/**
* 判断数据源是否存在
*/
public boolean existsDataSource(String dataSourceKey) {
return dataSourceMap.containsKey(dataSourceKey);
}
/**
* 获取所有数据源的key
*/
public Set<String> getAllDataSourceKeys() {
return new HashSet<>(dataSourceMap.keySet());
}
}
```
#### 3.2.3 动态数据源切换器
```java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
@Component
public class DynamicDataSourceSwitcher {
@Autowired
private DynamicDataSourceManager dataSourceManager;
// 当前线程使用的数据源key
private static final ThreadLocal<String> CURRENT_DATASOURCE_KEY = new ThreadLocal<>();
/**
* 切换到指定数据源
*/
public void switchDataSource(String dataSourceKey) {
if (!dataSourceManager.existsDataSource(dataSourceKey)) {
throw new RuntimeException("数据源" + dataSourceKey + "不存在");
}
CURRENT_DATASOURCE_KEY.set(dataSourceKey);
}
/**
* 获取当前数据源
*/
public DataSource getCurrentDataSource() {
String dataSourceKey = CURRENT_DATASOURCE_KEY.get();
if (dataSourceKey == null) {
throw new RuntimeException("当前没有设置数据源");
}
return dataSourceManager.getDataSource(dataSourceKey);
}
/**
* 清除当前线程的数据源设置
*/
public void clearDataSource() {
CURRENT_DATASOURCE_KEY.remove();
}
/**
* 获取当前数据源的key
*/
public String getCurrentDataSourceKey() {
return CURRENT_DATASOURCE_KEY.get();
}
}
```
#### 3.2.4 动态数据源注解
```java
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {}
```
#### 3.2.5 动态数据源AOP切面
```java
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DataSourceAspect {
@Autowired
private DynamicDataSourceSwitcher dataSourceSwitcher;
// 定义切点
@Pointcut("@annotation(com.example.annotation.DataSource)")
public void dataSourcePointCut() {}
// 环绕通知
@Around("dataSourcePointCut() && @annotation(dataSource)")
public Object around(ProceedingJoinPoint point, DataSource dataSource) throws Throwable {
try {
// 切换数据源
dataSourceSwitcher.switchDataSource(dataSource.value());
// 执行目标方法
return point.proceed();
} finally {
// 清除数据源设置
dataSourceSwitcher.clearDataSource();
}
}
}
```
## 第4章 从不同数据源读写数据
### 4.1 使用JdbcTemplate操作多数据源
#### 4.1.1 服务层实现
```java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
@Qualifier("primaryJdbcTemplate")
private JdbcTemplate primaryJdbcTemplate;
@Autowired
@Qualifier("secondaryJdbcTemplate")
private JdbcTemplate secondaryJdbcTemplate;
/**
* 从主数据源获取用户信息
*/
public User getUserFromPrimary(Long id) {
return primaryJdbcTemplate.queryForObject("SELECT * FROM user WHERE id = ?",
new Object[]{id}, (rs, rowNum) -> {
User user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
user.setAge(rs.getInt("age"));
return user;
});
}
/**
* 从从数据源获取用户信息
*/
public User getUserFromSecondary(Long id) {
return secondaryJdbcTemplate.queryForObject("SELECT * FROM user WHERE id = ?",
new Object[]{id}, (rs, rowNum) -> {
User user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
user.setAge(rs.getInt("age"));
return user;
});
}
/**
* 向主数据源插入用户信息
*/
public void insertUserToPrimary(User user) {
primaryJdbcTemplate.update("INSERT INTO user (name, age) VALUES (?, ?)",
user.getName(), user.getAge());
}
/**
* 向从数据源插入用户信息
*/
public void insertUserToSecondary(User user) {
secondaryJdbcTemplate.update("INSERT INTO user (name, age) VALUES (?, ?)",
user.getName(), user.getAge());
}
}
```
### 4.2 使用MyBatis操作多数据源
#### 4.2.1 Mapper接口
```java
// 主数据源Mapper
public interface PrimaryUserMapper {
User selectById(Long id);
int insert(User user);
}
// 从数据源Mapper
public interface SecondaryUserMapper {
User selectById(Long id);
int insert(User user);
}
```
#### 4.2.2 服务层实现
```java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private PrimaryUserMapper primaryUserMapper;
@Autowired
private SecondaryUserMapper secondaryUserMapper;
/**
* 从主数据源获取用户信息
*/
public User getUserFromPrimary(Long id) {
return primaryUserMapper.selectById(id);
}
/**
* 从从数据源获取用户信息
*/
public User getUserFromSecondary(Long id) {
return secondaryUserMapper.selectById(id);
}
/**
* 向主数据源插入用户信息
*/
@Transactional(transactionManager = "primaryTransactionManager")
public void insertUserToPrimary(User user) {
primaryUserMapper.insert(user);
}
/**
* 向从数据源插入用户信息
*/
@Transactional(transactionManager = "secondaryTransactionManager")
public void insertUserToSecondary(User user) {
secondaryUserMapper.insert(user);
}
}
```
### 4.3 使用动态数据源操作数据
#### 4.3.1 动态数据源服务实现
```java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class DynamicUserService {
@Autowired
private DynamicDataSourceManager dataSourceManager;
@Autowired
private DynamicDataSourceSwitcher dataSourceSwitcher;
/**
* 添加新的数据源
*/
public void addDataSource(String dataSourceKey, DynamicDataSourceConfig config) {
dataSourceManager.addDataSource(dataSourceKey, config);
}
/**
* 从指定数据源获取用户信息
*/
public User getUserFromDataSource(String dataSourceKey, Long id) {
try {
// 切换数据源
dataSourceSwitcher.switchDataSource(dataSourceKey);
// 获取当前数据源的JdbcTemplate
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSourceSwitcher.getCurrentDataSource());
// 查询数据
return jdbcTemplate.queryForObject("SELECT * FROM user WHERE id = ?",
new Object[]{id}, (rs, rowNum) -> {
User user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
user.setAge(rs.getInt("age"));
return user;
});
} finally {
// 清除数据源设置
dataSourceSwitcher.clearDataSource();
}
}
/**
* 向指定数据源插入用户信息
*/
public void insertUserToDataSource(String dataSourceKey, User user) {
try {
// 切换数据源
dataSourceSwitcher.switchDataSource(dataSourceKey);
// 获取当前数据源的JdbcTemplate
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSourceSwitcher.getCurrentDataSource());
// 插入数据
jdbcTemplate.update("INSERT INTO user (name, age) VALUES (?, ?)",
user.getName(), user.getAge());
} finally {
// 清除数据源设置
dataSourceSwitcher.clearDataSource();
}
}
/**
* 删除数据源
*/
public void removeDataSource(String dataSourceKey) {
dataSourceManager.removeDataSource(dataSourceKey);
}
}
```
#### 4.3.2 控制器实现
```java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
private DynamicUserService dynamicUserService;
/**
* 添加数据源
*/
@PostMapping("/data-source")
public String addDataSource(@RequestBody DataSourceRequest request) {
DynamicDataSourceConfig config = new DynamicDataSourceConfig();
config.setDriverClassName(request.getDriverClassName());
config.setUrl(request.getUrl());
config.setUsername(request.getUsername());
config.setPassword(request.getPassword());
dynamicUserService.addDataSource(request.getDataSourceKey(), config);
return "数据源添加成功";
}
/**
* 从指定数据源获取用户信息
*/
@GetMapping("/{dataSourceKey}/{id}")
public User getUser(@PathVariable String dataSourceKey, @PathVariable Long id) {
return dynamicUserService.getUserFromDataSource(dataSourceKey, id);
}
/**
* 向指定数据源插入用户信息
*/
@PostMapping("/{dataSourceKey}")
public String insertUser(@PathVariable String dataSourceKey, @RequestBody User user) {
dynamicUserService.insertUserToDataSource(dataSourceKey, user);
return "用户添加成功";
}
/**
* 删除数据源
*/
@DeleteMapping("/data-source/{dataSourceKey}")
public String removeDataSource(@PathVariable String dataSourceKey) {
dynamicUserService.removeDataSource(dataSourceKey);
return "数据源删除成功";
}
}
// 数据源请求参数类
class DataSourceRequest {
private String dataSourceKey;
private String driverClassName;
private String url;
private String username;
private String password;
// getter/setter省略
}
```
## 第5章 事务管理
### 5.1 配置多事务管理器
```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);
}
}
```
### 5.2 在多数据源中使用事务
```java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class TransactionalUserService {
@Autowired
private PrimaryUserMapper primaryUserMapper;
@Autowired
private SecondaryUserMapper secondaryUserMapper;
/**
* 在主数据源中执行事务
*/
@Transactional(transactionManager = "primaryTransactionManager")
public void createUserInPrimary(User user) {
primaryUserMapper.insert(user);
// 如果需要,可以在此处添加其他操作
}
/**
* 在从数据源中执行事务
*/
@Transactional(transactionManager = "secondaryTransactionManager")
public void createUserInSecondary(User user) {
secondaryUserMapper.insert(user);
// 如果需要,可以在此处添加其他操作
}
/**
* 在两个数据源中执行事务(分布式事务)
* 注意:这只是一个简单示例,实际的分布式事务需要更复杂的处理
*/
public void createUserInBothDataSources(User user) {
try {
// 先在主数据源中创建用户
createUserInPrimary(user);
// 再在从数据源中创建用户
createUserInSecondary(user);
} catch (Exception e) {
// 处理异常,可能需要进行补偿操作
throw new RuntimeException("创建用户失败:" + e.getMessage(), e);
}
}
}
```
### 5.3 使用分布式事务
对于需要在多个数据源之间保证数据一致性的场景,可以使用分布式事务框架,如Atomikos、Bitronix等。
#### 5.3.1 配置Atomikos分布式事务管理器
```java
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.jta.JtaTransactionManager;
import javax.transaction.UserTransaction;
@Configuration
public class AtomikosTransactionManagerConfig {
@Bean(initMethod = "init", destroyMethod = "close")
public UserTransactionManager userTransactionManager() {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(false);
return userTransactionManager;
}
@Bean
public UserTransaction userTransaction() throws Exception {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(300);
return userTransactionImp;
}
@Bean
public JtaTransactionManager transactionManager(
UserTransactionManager userTransactionManager,
UserTransaction userTransaction) {
JtaTransactionManager transactionManager = new JtaTransactionManager();
transactionManager.setTransactionManager(userTransactionManager);
transactionManager.setUserTransaction(userTransaction);
return transactionManager;
}
}
```
#### 5.3.2 配置Atomikos数据源
```java
import com.atomikos.jdbc.AtomikosDataSourceBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.util.Properties;
@Configuration
public class AtomikosDataSourceConfig {
@Value("${spring.datasource.primary.url}")
private String primaryUrl;
@Value("${spring.datasource.primary.username}")
private String primaryUsername;
@Value("${spring.datasource.primary.password}")
private String primaryPassword;
@Value("${spring.datasource.secondary.url}")
private String secondaryUrl;
@Value("${spring.datasource.secondary.username}")
private String secondaryUsername;
@Value("${spring.datasource.secondary.password}")
private String secondaryPassword;
// 主数据源
@Bean(name = "primaryDataSource")
public DataSource primaryDataSource() {
AtomikosDataSourceBean dataSource = new AtomikosDataSourceBean();
dataSource.setUniqueResourceName("primaryDB");
dataSource.setXaDataSourceClassName("com.mysql.cj.jdbc.MysqlXADataSource");
Properties properties = new Properties();
properties.setProperty("url", primaryUrl);
properties.setProperty("user", primaryUsername);
properties.setProperty("password", primaryPassword);
dataSource.setXaProperties(properties);
dataSource.setMaxPoolSize(20);
dataSource.setMinPoolSize(5);
return dataSource;
}
// 从数据源
@Bean(name = "secondaryDataSource")
public DataSource secondaryDataSource() {
AtomikosDataSourceBean dataSource = new AtomikosDataSourceBean();
dataSource.setUniqueResourceName("secondaryDB");
dataSource.setXaDataSourceClassName("com.mysql.cj.jdbc.MysqlXADataSource");
Properties properties = new Properties();
properties.setProperty("url", secondaryUrl);
properties.setProperty("user", secondaryUsername);
properties.setProperty("password", secondaryPassword);
dataSource.setXaProperties(properties);
dataSource.setMaxPoolSize(20);
dataSource.setMinPoolSize(5);
return dataSource;
}
}
```
## 第6章 最佳实践与注意事项
### 6.1 多数据源最佳实践
1. **数据源连接池配置**:为每个数据源配置合适的连接池大小,避免资源浪费
```yaml
spring:
datasource:
primary:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
```
2. **数据源健康检查**:定期检查数据源连接状态
```java
@Component
public class DataSourceHealthChecker {
@Autowired
private DynamicDataSourceManager dataSourceManager;
@Scheduled(fixedRate = 60000) // 每分钟检查一次
public void checkDataSources() {
Set<String> dataSourceKeys = dataSourceManager.getAllDataSourceKeys();
for (String key : dataSourceKeys) {
try {
DataSource dataSource = dataSourceManager.getDataSource(key);
Connection connection = dataSource.getConnection();
// 执行简单查询检查连接是否有效
PreparedStatement ps = connection.prepareStatement("SELECT 1");
ps.executeQuery();
ps.close();
connection.close();
System.out.println("数据源" + key + "连接正常");
} catch (Exception e) {
System.err.println("数据源" + key + "连接异常:" + e.getMessage());
// 可以添加告警通知
}
}
}
}
```
3. **使用读写分离**:主数据库负责写操作,从数据库负责读操作
```java
@Service
public class ReadWriteUserService {
@Autowired
@Qualifier("primaryJdbcTemplate")
private JdbcTemplate writeJdbcTemplate;
@Autowired
@Qualifier("secondaryJdbcTemplate")
private JdbcTemplate readJdbcTemplate;
// 写操作使用主数据源
public void saveUser(User user) {
writeJdbcTemplate.update("INSERT INTO user (name, age) VALUES (?, ?)",
user.getName(), user.getAge());
}
// 读操作使用从数据源
public User getUser(Long id) {
return readJdbcTemplate.queryForObject("SELECT * FROM user WHERE id = ?",
new Object[]{id}, (rs, rowNum) -> {
User user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
user.setAge(rs.getInt("age"));
return user;
});
}
}
```
4. **使用动态数据源进行分库分表**:根据业务规则动态选择数据源
```java
@Service
public class ShardingUserService {
@Autowired
private DynamicDataSourceManager dataSourceManager;
@Autowired
private DynamicDataSourceSwitcher dataSourceSwitcher;
/**
* 根据用户ID选择数据源
*/
private String getDataSourceKeyByUserId(Long userId) {
// 简单的分库规则:根据用户ID的奇偶性选择数据源
return userId % 2 == 0 ? "db_even" : "db_odd";
}
/**
* 保存用户,根据用户ID选择数据源
*/
public void saveUser(User user) {
String dataSourceKey = getDataSourceKeyByUserId(user.getId());
try {
dataSourceSwitcher.switchDataSource(dataSourceKey);
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSourceSwitcher.getCurrentDataSource());
jdbcTemplate.update("INSERT INTO user (id, name, age) VALUES (?, ?, ?)",
user.getId(), user.getName(), user.getAge());
} finally {
dataSourceSwitcher.clearDataSource();
}
}
/**
* 获取用户,根据用户ID选择数据源
*/
public User getUser(Long id) {
String dataSourceKey = getDataSourceKeyByUserId(id);
try {
dataSourceSwitcher.switchDataSource(dataSourceKey);
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSourceSwitcher.getCurrentDataSource());
return jdbcTemplate.queryForObject("SELECT * FROM user WHERE id = ?",
new Object[]{id}, (rs, rowNum) -> {
User user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
user.setAge(rs.getInt("age"));
return user;
});
} finally {
dataSourceSwitcher.clearDataSource();
}
}
}
```
### 6.2 注意事项
1. **资源管理**:动态创建的数据源需要妥善管理,避免资源泄漏
```java
// 在应用关闭时关闭所有数据源
@PreDestroy
public void destroy() {
for (Map.Entry<String, DataSource> entry : dataSourceMap.entrySet()) {
DataSource dataSource = entry.getValue();
if (dataSource instanceof HikariDataSource) {
((HikariDataSource) dataSource).close();
}
}
dataSourceMap.clear();
}
```
2. **事务边界**:明确事务边界,避免跨多个数据源的事务操作
```java
// 错误的做法:一个事务中操作多个数据源
@Transactional
public void wrongMethod(User user) {
// 操作主数据源
primaryUserMapper.insert(user);
// 操作从数据源
secondaryUserMapper.insert(user);
}
// 正确的做法:分别在各自的事务中操作
public void correctMethod(User user) {
// 在主数据源事务中操作
primaryTransactionTemplate.execute(status -> {
primaryUserMapper.insert(user);
return null;
});
// 在从数据源事务中操作
secondaryTransactionTemplate.execute(status -> {
secondaryUserMapper.insert(user);
return null;
});
}
```
3. **数据源监控**:监控每个数据源的连接使用情况
```java
@RestController
@RequestMapping("/api/data-source")
public class DataSourceMonitorController {
@Autowired
private DynamicDataSourceManager dataSourceManager;
/**
* 获取所有数据源的连接池信息
*/
@GetMapping("/monitor")
public Map<String, DataSourceStats> getDataSourceStats() {
Map<String, DataSourceStats> statsMap = new HashMap<>();
Set<String> dataSourceKeys = dataSourceManager.getAllDataSourceKeys();
for (String key : dataSourceKeys) {
DataSource dataSource = dataSourceManager.getDataSource(key);
if (dataSource instanceof HikariDataSource) {
HikariDataSource hikariDataSource = (HikariDataSource) dataSource;
DataSourceStats stats = new DataSourceStats();
stats.setActiveConnections(hikariDataSource.getHikariPoolMXBean().getActiveConnections());
stats.setIdleConnections(hikariDataSource.getHikariPoolMXBean().getIdleConnections());
stats.setTotalConnections(hikariDataSource.getHikariPoolMXBean().getTotalConnections());
stats.setMaxPoolSize(hikariDataSource.getMaximumPoolSize());
stats.setMinIdle(hikariDataSource.getMinimumIdle());
statsMap.put(key, stats);
}
}
return statsMap;
}
}
// 数据源统计信息类
class DataSourceStats {
private int activeConnections;
private int idleConnections;
private int totalConnections;
private int maxPoolSize;
private int minIdle;
// getter/setter省略
}
```
4. **异常处理**:为数据源操作提供完善的异常处理机制
```java
@Service
public class ExceptionHandlingService {
private static final Logger logger = LoggerFactory.getLogger(ExceptionHandlingService.class);
@Autowired
private DynamicUserService dynamicUserService;
/**
* 带异常处理的获取用户方法
*/
public User getUserWithExceptionHandling(String dataSourceKey, Long id) {
try {
return dynamicUserService.getUserFromDataSource(dataSourceKey, id);
} catch (RuntimeException e) {
logger.error("获取用户失败,数据源:{}", dataSourceKey, e);
// 可以根据异常类型进行不同处理
if (e.getMessage().contains("数据源")) {
throw new DataSourceException("数据源不可用:" + dataSourceKey, e);
}
throw new BusinessException("获取用户失败:" + e.getMessage(), e);
}
}
}
// 自定义异常类
class DataSourceException extends RuntimeException {
public DataSourceException(String message, Throwable cause) {
super(message, cause);
}
}
class BusinessException extends RuntimeException {
public BusinessException(String message, Throwable cause) {
super(message, cause);
}
}
```
## 第7章 完整示例项目
### 7.1 项目结构
```
src/main/java/com/example/multidatasource/
├── config/
│ ├── DataSourceConfig.java
│ ├── DynamicDataSourceConfig.java
│ ├── DynamicDataSourceManager.java
│ ├── DynamicDataSourceSwitcher.java
│ ├── TransactionManagerConfig.java
│ └── WebConfig.java
├── controller/
│ ├── DataSourceController.java
│ └── UserController.java
├── service/
│ ├── DataSourceService.java
│ ├── UserService.java
│ └── impl/
│ ├── DataSourceServiceImpl.java
│ └── UserServiceImpl.java
├── mapper/
│ ├── primary/
│ │ └── PrimaryUserMapper.java
│ └── secondary/
│ └── SecondaryUserMapper.java
├── model/
│ ├── User.java
│ └── vo/
│ ├── DataSourceStatsVO.java
│ └── ResponseVO.java
├── annotation/
│ └── DataSource.java
├── aspect/
│ └── DataSourceAspect.java
└── MultiDataSourceApplication.java
src/main/resources/
├── application.yml
└── mapper/
├── primary/
│ └── PrimaryUserMapper.xml
└── secondary/
└── SecondaryUserMapper.xml
```
### 7.2 核心依赖(pom.xml)
```xml
<dependencies>
<!-- Spring Boot 核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Web依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JDBC依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- MyBatis依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- HikariCP连接池 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- AOP依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 分布式事务(可选) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
```
### 7.3 应用主类
```java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class MultiDataSourceApplication {
public static void main(String[] args) {
SpringApplication.run(MultiDataSourceApplication.class, args);
}
}
```
### 7.4 数据源配置示例(application.yml)
```yaml
spring:
datasource:
# 主数据源配置
primary:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_primary?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
username: root
password: root
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
# 从数据源配置
secondary:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db_secondary?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
username: root
password: root
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
# MyBatis配置
mybatis:
type-aliases-package: com.example.multidatasource.model
mapper-locations: classpath:mapper/**/*.xml
configuration:
map-underscore-to-camel-case: true
# 日志配置
logging:
level:
com.example.multidatasource: debug
org.springframework.jdbc.core: debug
# 服务器配置
server:
port: 8080
servlet:
context-path: /multi-datasource
```
## 第8章 总结与展望
### 8.1 总结
Spring多数据源技术是企业级应用开发中的重要技能,本文详细介绍了Spring中实现多数据源的两种主要方式:静态多数据源配置和动态创建数据源。
1. **静态多数据源配置**:通过配置文件预定义多个数据源,使用注解或代码方式在不同数据源之间切换
2. **动态创建数据源**:在运行时根据业务需求动态创建和管理数据源,这种方式更加灵活,可以满足复杂的业务场景
3. **事务管理**:针对多数据源环境,需要特别注意事务管理,可以使用多个事务管理器或分布式事务框架
### 8.2 技术展望
随着微服务架构的普及和云原生技术的发展,多数据源技术也在不断演进:
1. **服务网格**:使用Service Mesh(如Istio)实现服务间的数据访问和事务协调
2. **分布式数据库中间件**:使用分库分表中间件(如ShardingSphere)简化多数据源管理
3. **云原生数据库**:利用云服务提供商提供的托管数据库服务(如AWS RDS、阿里云RDS)降低多数据源管理复杂度
4. **数据虚拟化**:通过数据虚拟化技术(如Denodo、TIBCO Data Virtualization)屏蔽底层数据源差异,提供统一的数据访问接口
对于开发者来说,掌握多数据源技术不仅可以解决实际项目中的复杂数据访问需求,还可以为构建高性能、高可用的企业应用提供有力支持。在实际项目中,应根据具体需求选择合适的多数据源实现方式,并结合最佳实践进行设计和开发。
2万+

被折叠的 条评论
为什么被折叠?



