开发者

一文详解SpringBoot实现多数据源的自动切换

目录
  • 1. 引言
  • 2. 环境准备
  • 3. 添加依赖
  • 4. 配置多数据源
  • 5. 创建数据源配置类
  • 6. 实现动态数据源切换
  • 7. 使用AOP实现自动切换
  • 8. 测试
  • 9.方法补充
    • 添加依赖
    • 配置多数据源
    • 创建数据源配置类
    • 创建动态数据源类
    • 创建数据源上下文管理类
    • 使用 AOP 切换数据源
    • 自定义注解
    • 使用示例

在现代企业级应用中,为了提高系统的可维护性和扩展性,多数据源的配置和使用变得越来越常见。特别是在处理跨数据库操作、读写分离、分库分表等场景时,动态数据源切换技术显得尤为重要。本文将介绍如何在Spring Boot项目中实现多数据源的自动切换。

1. 引言

随着业务的不断增长,单一的数据源往往难以满足高并发、大数据量的需求。通过引入多数据源,可以有效地解决这些问题。Spring Boot提供了灵活的配置机制,使得多数据源的管理变得更加简便。

2. 环境准备

Java版本:1.8+

Spring Boot版本:2.3.4.RELEASE

数据库:mysql 5.7+

3. 添加依赖

首先,在​​pom.XML​​文件中添加必要的依赖:

<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Spring Boot JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- MySQL Connector -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- HikariCP 数据源 -->
    &pythonlt;dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
    </dependency>
</dependencies>

4. 配置多数据源

在​​application.yml​​中配置多个数据源:

spring:
  datasourcejavascript:
    master:
      url: jdbc:mysql://localhost:3306/db_master?useUnicode=true&characterEncoding=utf-8&useSSL=false
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
    slave:
      url: jdbc:mysql://localhost:3306/db_slave?useUnicode=true&characterEncoding=utf-8&useSSL=false
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver

5. 创建数据源配置类

创建一个配置类来管理多个数据源,并将其注册到Spring容器中:

import com.zaxxer.hikari.HikariDataSource;
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.jdbc.datasource.lookup.AbstractRoutingDataSource;

@Configuration
public class DataSourceConfig {

    @Bean(name = "masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public HikariDataSource masterDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }

    @Bean(name = "slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public HikariDataSource slaveDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }

    @Bean(name = "dynamicDataSource")
    public AbstractRoutingDataSource dynamicDataSource(@Qualifier("masterDataSource") HikariDataSource masterDataSource,
                                                      @Qualifier("slaveDataSource") HikariDataSource slaveDataSource) {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put("master", masterDataSource);
        dataSourceMap.put("slave", slaveDataSource);
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        return dynamicDataSource;
    }
}

6. 实现动态数据源切换

创建一个​​DynamicDataSource​​类继承​​AbstractRoutingDataSource​​,并重写​​determineCurrentLookupKey​​方法来决定当前使用的数据源:

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}

创建一个​​DataSourceContextHolder​​类来管理数据源上下文:

public class DataSourceContextHolder {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setDataSource(String dataSource) {
        contextHolder.set(dataSource);
    }

    public static String getDataSource() {
        return contextHolder.get();
    }

    public static void clearDataSource() {
        contextHolder.remove();
    }
}

7. 使用AOP实现自动切换

使用Spring AOP来实现数据源的自动切换:

import org.ASPectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class DataSourceAspect {

    @Pointcut("@annotation(com.example.demo.annotation.TargetDataSource)")
    public void dataSourcePointCut() {}

    @Before("dataSourcePointCut()")
    public void switchDataSource(JoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        TargetDataSource targetDataSource = signature.getMethod().getAnnotation(TargetDataSource.class);
        if (targetDataSource != null) {
            DataSourceContextHolder.setDataSource(targetDataSource.value());
        }
    }
}

创建一个自定义注解​​TargetDataSource​​用于标记需要切换数据源的方法:

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
    String value();
}

8. 测试

创建一些测试用的服务类和控制器来验证多数据源切换是否正常工作:

import com.example.demo.annotation.TargetDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @TargetDataSource("master")
    public List<Map<String, Object>> getUserFromMaster() {
        return jdbcTemplate.queryForList("SELECT * FROM user");
    }

    @TargetDataSource("slave")
    public List<Map<String, Object>> getUserFromSlave() {
        return jdbcTemplate.queryForList("SELECT * FROM user");
    }
}

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/master")
    public List<Map<String, Object>> getUsersFromMaster() {
        return userService.getUserFromMaster();
    }

   DRPLpwTRp @GetMapping("/slave")
    public List<Map<String, Object>> getUsersFromSlave() {
        return userService.getUserFromSlave();
    }
}

9.方法补充

在实际应用中,特别是在大型系统或微服务架构中,经常会遇到需要操作多个数据库的情况。Spring Boot 提供了灵活的配置和扩展机制,可以方便地实现多数据源的支持。下面是一个简单的示例,展示如何在 Spring Boot 应用中配置和使用动态数据源。

添加依赖

首先,在 ​​pom.xml​​ 文件中添加必要的依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
    </dependency>
</dependencies>

配置多数据源

在 ​​application.yml​​ 中配置两个数据源:

spring:
  datasource:
    primary:
      url: jdbc:mysql://localhost:3306/db1?useSSL=false&serverTimezone=UTC
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
    secondary:
      url: jdbc:mysql://localhost:3306/DB2?useSSL=false&serverTimezone=UTC
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver

创建数据源配置类

创建一个配置类来配置多个数据源,并将它们注册到 Spring 容器中:

import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties("spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }

    @Bean
    @Primary
    public DynamicDataSource dataSource(@Qualifier("primaryDataSource") DataSource primaryDataSource,
                                        @Qualifier("secondaryDataSource") DataSource secondaryDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("primary", primaryDataSource);
        targetDataSources.put("secondary", secondaryDataSource);
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(primaryDataSource);
        return dynamicDataSource;
    }
}

创建动态数据源类

创建一个继承自 ​​AbstractRoutingDataSource​​ 的类来实现数据源的动态切换:

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}

创建数据源上下文管理类

创建一个工具类来管理数据源的切换:

public class DataSourceContextHolder {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setDataSource(String dataSource) {
        contextHolder.set(dataSource);
    }

    public static String getDataSource() {
        return contextHolder.get();
    }

    public static void clearDataSource() {
        contextHolder.remove();
    }
}

使用 AOP 切换数据源

使用 AOP 来切面管理数据源的切换,例如根据方法注解来切换数据源:

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class DataSourceAspect {

    @Before("@annotation(com.example.demo.annotation.TargetDataSource)")
    public void changeDataSource(JoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        TargetDataSource targetDataSource = signature.getMethod().getAnnotation(TargetDataSource.class);
        if (targetDataSource != null) {
            DataSourceContextHolder.setDataSource(targetDataSource.value());
        }
    }

    @After("@annotation(com.example.demo.annotation.TargetDataSource)")
    public void restoreDataSource(JoinPoint point) {
        DataSourceContextHolder.clearDataSource();
    }
}

自定义注解

创建一个自定义注解来标记需要切换数据源的方法:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {
    String value();
}

使用示例

在服务层中使用自定义注解来切换数据源:

import com.example.demo.annotation.TargetDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @TargetDataSource("primary")
    public void DOSomethingWithPrimaryDB() {
        jdbcTemplate.execute("SELECT * FROM user");
    }

    @TargetDataSource("secondary")
    public void doSomethingWithSecondaryDB() {
        jdbcTemplate.execute("SELECT * FROM user");
    }
}

方法二:

在Spring Boot应用中实现多数据源的动态切换是一个常见的需求,尤其是在需要访问多个数据库或不同环境下的数据库时。下面将详细介绍如何在Spring Boot中配置和使用动态数据源。

添加依赖

首先,确保你的​​pom.xml​​文件中包含必要的依赖项。通常情况下,你需要Spring Boot的Web和JPA/MyBATis等ORM框架的依赖,以及相应的数据库驱动依赖。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.23</version>
    </dependency>
    <!-- 其他数据库驱动依赖 -->
</dependencies>

配置数据源

在​​application.yml​​或​​application.properties​​文件中配置多个数据源。

spring:
  datasource:
    primary:
      url: jdbc:mysql://localhost:3306/db1?useSSL=false&serverTimezone=UTC
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
    secondary:
      url: jdbc:mysql://localhost:3306/db2?useSSL=false&serverTimezone=UTC
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver

创建数据源配置类

创建一个配置类来定义和配置多个数据源。

import org.apache.commons.dbcp2.BasicDataSource;
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.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

imporphpt javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return new BasicDataSource();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return new BasicDataSource();
    }

    @Bean
    public DataSource dynamicDataSource(@Qualifier("primaryDataSource") DataSource primaryDataSource,
                                        @Qualifier("secondaryDataSource") DataSource secondaryDataSource) {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("primary", primaryDataSource);
        targetDataSources.put("secondary", secondaryDataSource);
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(primaryDataSource);
        return dynamicDataSource;
    }

    @Bean
    public PlatformTransactionManager transactionManager(DynamicDataSource dynamicDataSource) {
        return new DataSourceTransactionManager(dynamicDataSource);
    }
}

创建动态数据源类

创建一个自定义的​​DynamicDataSource​​类,用于管理多个数据源的切换。

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}

创建数据源上下文持有类

创建一个线程安全的上下文持有类,用于存储当前线程的数据源标识。

public class DataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setDataSource(String dataSource) {
        contextHolder.set(dataSource);
    }

    public static String getDataSource() {
        return contextHolder.get();
    }

    public static void clearDataSource() {
        contextHolder.remove();
    }
}

创建数据源切换注解

创建一个注解,用于在方法或类上标记数据源切换。

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String value() default "";
}

创建切面类

创建一个切面类,用于拦截带有​​@DataSource​​注解的方法或类,并进行数据源切换。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class DataSourceAspect {

    @Pointcut("@annotation(com.example.demo.DataSource)")
    public void dataSourcePointCut() {}

    @Before("dataSourcePointCut() && @annotation(dataSource)")
    public void switchDataSource(DataSource dataSource) {
        DataSourceContextHolder.setDataSource(dataSource.value());
    }
}

使用示例

在Service层或Controller层使用​​@DataSource​​注解来切换数据源。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @DataSource("primary")
    public User getUserFromPrimary(int id) {
        return userRepository.findById(id).orElse(null);
    }

    @DataSource("secondary")
    public User getUserFromSecondary(int id) {
        return userRepository.findById(id).orElse(null);
    }
}

配置JPA或MyBatis

如果你使用的是JPA或MyBatis,需要确保它们能够正确地使用动态数据源。对于JPA,你可能需要配置实体管理器工厂;对于MyBatis,需要配置SqlSessionFactory。

JPA配置

import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
    basePackages = "com.example.demo.repository",
    entityManagerFactoryRef = "entityManagerFactory",
    transactionManagerRef = "transactionManager"
)
public class JpaConfig {

    @Autowired
    private JpaProperties jpaProperties;

    @Autowired
    @Qualifier("dynamicDataSource")
    private DataSource dynamicDataSource;

    @Primary
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(dynamicDataSource)
                .properties(jpaProperties.getProperties())
                .packages("com.example.demo.entity")
                .persistenceUnit("default")
                .build();
    }

    @Primary
    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}

MyBatis配置

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.demo.mapper", sqlSessionTemplateRef = "sqlSessionTemplate")
public class MyBatisConfig {

    @Autowired
    @Qualifier("dynamicDataSource")
    private DataSource dynamicDataSource;

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dynamicDataSource);
        factoryBean.setMapperlocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        return factoryBean.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

通过以上步骤,你可以在Spring Boot应用中实现多数据源的动态编程切换。

到此这篇关于一文详解SpringBoot实现多数据源的自动切换的文章就介绍到这了,更多相关SpringBoot数据源自动切换内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新开发

开发排行榜