Spring多数据源完全指南

# 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)屏蔽底层数据源差异,提供统一的数据访问接口

对于开发者来说,掌握多数据源技术不仅可以解决实际项目中的复杂数据访问需求,还可以为构建高性能、高可用的企业应用提供有力支持。在实际项目中,应根据具体需求选择合适的多数据源实现方式,并结合最佳实践进行设计和开发。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值