Spring配置多数据源切换

下面我将详细讲解Spring配置多数据源切换的完整攻略。处理多数据源切换的核心是通过动态切换数据源来实现。实现这一点的最简单、最常用的方法是使用AOP切面,这也是本文的重点。

1. 添加依赖

以下是maven引用多数据源相关依赖的代码:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-jdbc</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>
   <scope>runtime</scope>
</dependency>

<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>druid-spring-boot-starter</artifactId>
   <version>1.1.10</version>
</dependency>

<dependency>
   <groupId>org.mybatis.spring.boot</groupId>
   <artifactId>mybatis-spring-boot-starter</artifactId>
   <version>2.1.4</version>
</dependency>

添加完依赖后,需要在application.yml文件中配置相应的数据库信息。

2. 配置多数据源

在application.yml中添加数据源配置,例如:

spring:
    datasource:
      master:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/master?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
        username: root
        password: root
      slave:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/slave?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
        username: root
        password: root

上面的配置中定义了两个数据源,一个是master,一个是slave。

3. 动态切换数据源

这里的动态指的是在编写代码时不知道使用哪个数据源,运行时动态选择。需要通过AOP来实现动态切换数据源。

首先,新建一个切面类,如下所示:

@Aspect
@Component
public class DataSourceAspect {

    @Pointcut("@annotation(com.example.multipledatasource.config.DataSource)")
    public void pointCut() {
    }

    @Before("pointCut() && @annotation(ds)")
    public void doBefore(DataSource ds) throws Throwable {
        DataSourceEnum value = ds.value();
        if (value == DataSourceEnum.Master) {
            DynamicDataSourceContextHolder.setDataSourceKey("master");
        } else if (value == DataSourceEnum.Slave) {
            DynamicDataSourceContextHolder.setDataSourceKey("slave");
        }
    }

    @After("pointCut()")
    public void doAfter() {
        DynamicDataSourceContextHolder.clearDataSourceKey();
    }
}

上述代码中的切面会拦截所有的@DataSource注解的方法调用,并根据该注解中给定的数据源类型,动态切换数据源。

下一步是创建一个注解@DataSource,如下所示:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {

    DataSourceEnum value() default DataSourceEnum.Master;
}

该注解指定了被拦截方法应该使用哪个数据源。DataSourceEnum是一个枚举类型,包含了所有的数据源。例如:

public enum DataSourceEnum {

    Master("master"),
    Slave("slave");

    private String key;

    public String getKey() {
        return key;
    }

    DataSourceEnum(String key) {
        this.key = key;
    }
}

当我们需要在方法调用中使用不同的数据源时,只需要在方法前加上@DataSource注解,这样就可以动态切换数据源了。

以下是一个示例:

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    @DataSource(value = DataSourceEnum.Master)
    public User addUser(User user) {
        userMapper.insert(user);
        return user;
    }

    @Override
    @DataSource(value = DataSourceEnum.Slave)
    public User getUserById(int id) {
        return userMapper.selectByPrimaryKey(id);
    }
}

上面的示例中,addUser方法使用的是master数据源,getUserById方法使用的是slave数据源。

示例一

下面是一个简单的基于Spring Boot的多数据源切换示例,示例中使用的是JPA和Hibernate。

首先,我们需要定义两个数据源:

spring.datasource.master.url=jdbc:mysql://localhost:3306/master
spring.datasource.master.username=root
spring.datasource.master.password=123456
spring.datasource.master.driver-class-name=com.mysql.jdbc.Driver

spring.datasource.slave.url=jdbc:mysql://localhost:3306/slave
spring.datasource.slave.username=root
spring.datasource.slave.password=123456
spring.datasource.slave.driver-class-name=com.mysql.jdbc.Driver

然后在代码中注入多数据源配置,如下所示:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "entityManagerFactoryMaster",
        transactionManagerRef = "transactionManagerMaster",
        basePackages = {"com.example.multipledatasource.repository.master"})
public class MasterDataSourceConfig {

    @Primary
    @Bean(name = "dataSourceMaster")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource dataSourceMaster() {
        return DataSourceBuilder.create().build();
    }

    @Primary  
    @Bean(name = "entityManagerFactoryMaster")
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryMaster(
            EntityManagerFactoryBuilder builder,
            @Qualifier("dataSourceMaster") DataSource dataSource) {
        return builder
                .dataSource(dataSource)
                .packages("com.example.multipledatasource.entity.master")
                .persistenceUnit("master")
                .build();
    }

    @Primary  
    @Bean(name = "transactionManagerMaster")
    public PlatformTransactionManager transactionManagerMaster(
            @Qualifier("entityManagerFactoryMaster") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}

上述代码中定义了一个@Primary的数据源,这意味着这个数据源是主数据源。在JPA相关注解中,通过使用EntityManagerFactoryBuilder来指定特定的包和持久化单元。

接下来,我们定义另一个数据源:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "entityManagerFactorySlave",
        transactionManagerRef = "transactionManagerSlave",
        basePackages = {"com.example.multipledatasource.repository.slave"})
public class SlaveDataSourceConfig {

    @Bean(name = "dataSourceSlave")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource dataSourceSlave() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "entityManagerFactorySlave")
    public LocalContainerEntityManagerFactoryBean entityManagerFactorySlave(
            EntityManagerFactoryBuilder builder,
            @Qualifier("dataSourceSlave") DataSource dataSource) {
        return builder
                .dataSource(dataSource)
                .packages("com.example.multipledatasource.entity.slave")
                .persistenceUnit("slave")
                .build();
    }

    @Bean(name = "transactionManagerSlave")
    public PlatformTransactionManager transactionManagerSlave(
            @Qualifier("entityManagerFactorySlave") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}

配置相似,不同的是这个数据源没有设置@Primary,表示这个数据源是从数据源。

DataSourceAspect的实现如下所示:

@Aspect
@Component
public class DataSourceAspect {

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

    }

    @Before("dataSourcePointCut() ")
    public void dataSourceBefore(JoinPoint joinPoint) throws Exception {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Class<?> clazz = joinPoint.getTarget().getClass();
        DataSource.DS dataSource = clazz.isAnnotationPresent(DataSource.DS.class)
                ? clazz.getAnnotation(DataSource.DS.class) : null;
        if (dataSource == null) {
            dataSource = signature.getMethod().isAnnotationPresent(
                    DataSource.DS.class) ? signature.getMethod()
                    .getAnnotation(DataSource.DS.class) : null;
        }
        String dataSourceKey = DataSourceType.MASTER;
        if (dataSource != null) {
            dataSourceKey = dataSource.value().getKey();
        }
        DynamicDataSourceContextHolder.setDataSourceKey(dataSourceKey);
    }

    @After("dataSourcePointCut() ")
    public void dataSourceAfter() {
        DynamicDataSourceContextHolder.clearDataSourceKey();
    }

}

示例中的@DataSource注解被定义为一个@Qualifier:

@Qualifier
@Target({ElementType.FIELD,METHOD,PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {

    public static final String MASTER = "master";
    public static final String SLAVE = "slave";

    DS value() default DS.MASTER;

    @Scope("prototype")
    @Component
    public static class DS {

        /**
         * The value
         */
        private String value;

        /**
         * Instantiates a new Ds.
         *
         * @param value the value
         */
        public DS(final String value) {
            this.value = value;
        }

        /**
         * Gets key.
         *
         * @return the key
         */
        public String getKey() {
            return this.value;
        }

        /**
         * Master ds.
         *
         * @return the ds
         */
        public static DS MASTER() {
            return new DS(DataSource.MASTER);
        }

        /**
         * Slave ds.
         *
         * @return the ds
         */
        public static DS SLAVE() {
            return new DS(DataSource.SLAVE);
        }
    }

}

在repository类中,通过使用@DataSource注解来指定数据源:

@Repository
public interface UserRepository extends JpaRepository<User, Integer> {

    @DataSource(DataSource.DS.SLAVE)
    List<User> findAllByName(String name);

    @DataSource(DataSource.DS.SLAVE)
    User findByName(String name);

}

示例二

下面是另一个示例,示例中使用的是mybatis作为持久层框架。示例中定义了两个数据源,一个是master,一个是slave。

首先,我们定义了一个DynamicDataSource类,该类继承了AbstractRoutingDataSource,并且实现了determineCurrentLookupKey方法:

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDateSourceKey();
    }

}

接下来,我们需要定义两个数据源的bean:

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

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

然后,我们需要配置方式RoutingDataSource,如下所示:

@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource() {
    DynamicDataSource dynamicDataSource = new DynamicDataSource();
    Map<Object, Object> targetDataSources = new HashMap<>();
    targetDataSources.put(DataSourceEnum.MASTER.getName(), masterDataSource());
    targetDataSources.put(DataSourceEnum.SLAVE.getName(), slaveDataSource());
    dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
    dynamicDataSource.setTargetDataSources(targetDataSources);
    return dynamicDataSource;
}

上述代码中,我们创建了DynamicDataSource bean,并且将数据源加入到了数据源映射表中。

在service类的方法中使用@DataSource注解指定数据源,示例如下:

@DataSource(DataSourceEnum.SLAVE)
@Override
public List<User> selectAll() {
    return userDao.selectAll();
}

最后,我们需要在所有涉及到DataSource标签的方法上添加一个切面,来动态的切换数据源:

@Aspect
@Component
public class DynamicDataSourceAspect {

    @After("@annotation(ds)")
    public void afterSwitchDS(JoinPoint point, DataSource ds) {
        DynamicDataSourceContextHolder.resetDataSource();
    }

    @Before("@annotation(ds)")
    public void switchDS(JoinPoint point, DataSource ds) {
        if (ds == null || ds.value() == DataSourceEnum.MASTER) {
            DynamicDataSourceContextHolder.setDataSourceKey(DataSourceEnum.MASTER.getName());
        } else {
            DynamicDataSourceContextHolder.setDataSourceKey(DataSourceEnum.SLAVE.getName());
        }
    }

}

上述代码中的切面检查了当前的数据源,如果没有@DataSource注解,或者注解指定的数据源为master,就会将数据源切换到master,否则就切换到slave。

这样,在使用MySQL等数据库时,我们就可以通过切换数据源的方式来实现多数据源切换了。

在使用中,我们只需要在调用来自与需要使用其他数据源的dao或service方法时,加上相应的@DataSource注解即可。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Spring配置多数据源切换 - Python技术站

(0)
上一篇 2023年5月20日
下一篇 2023年5月20日

相关文章

  • Kafka 安装与配置详细过程

    下面是 Kafka 安装与配置的详细攻略: 安装 Kafka 下载 Kafka 压缩包: wget http://mirrors.ocf.berkeley.edu/apache/kafka/2.8.0/kafka_2.13-2.8.0.tgz 解压缩 Kafka 压缩包: tar -xzf kafka_2.13-2.8.0.tgz 进入解压后的 Kafka …

    Java 2023年5月20日
    00
  • 解决Maven本地仓库明明有对应的jar包但还是报找不到的问题

    当我们在使用 Maven 构建项目时,有时会出现 Maven 本地仓库中明明已经有对应的 jar 包,但是在使用时却提示找不到该依赖的情况。这种情况一般是因为 Maven 本地仓库的缓存出现问题,以下是解决该问题的几种方法和步骤: 方法一:清空 Maven 本地仓库缓存 打开命令行窗口并进入到 Maven 本地仓库目录,例如在 Windows 操作系统下,打…

    Java 2023年5月26日
    00
  • java中如何实现对类的对象进行排序

    针对 Java 中如何实现对类的对象进行排序,一般有两种常见的方式:实现 Comparable 接口或实现 Comparator 接口。下面会详细介绍这两种方式的实现方法及示例。 实现 Comparable 接口 实现 Comparable 接口的方式是让类自身具备排序能力,可以使用 Java 中的 Arrays.sort() 或 Collections.s…

    Java 2023年5月26日
    00
  • 详解java设计模式之六大原则

    详解Java设计模式之六大原则 在软件开发中,设计模式是一种解决特定问题的经验总结,它提供了一种通用的设计思路,可以使我们在编码时更加高效和准确地解决问题。Java设计模式是指在Java程序开发中应用的一种设计方式,它有六大原则,分别是: 单一职责原则 里氏替换原则 依赖倒置原则 接口隔离原则 迪米特原则 开闭原则 本文将详细讲解这六大原则。 单一职责原则 …

    Java 2023年5月26日
    00
  • IDEA多线程文件下载插件开发的步骤详解

    下面我会为你详细讲解“IDEA多线程文件下载插件开发的步骤详解”的完整攻略。整个过程将包含以下几个步骤: 确定要实现的功能 新建一个IntelliJ IDEA插件项目 编写代码,完成下载文件的功能 安装和调试插件 将插件打包发布 下面对每个步骤进行详细说明: 1. 确定要实现的功能 在开发插件之前,我们需要确定插件要实现的功能和使用场景。本篇攻略实现的功能是…

    Java 2023年5月26日
    00
  • java 对象的克隆(浅克隆和深克隆)

    Java 对象的克隆指的是创建一个与原始对象相同的新对象,但两个对象的引用地址是不同的。根据克隆的深度不同,可以分为浅克隆和深克隆两种。 浅克隆 浅克隆是指在克隆一个对象时,只复制对象中的基本类型数据和对象的引用地址,而不是复制对象中引用对象的内容。这意味着,克隆后的对象和原始对象共享引用对象,即对其中一个对象的更改会对另一个对象产生影响。 如何进行浅克隆 …

    Java 2023年5月26日
    00
  • 如何将javaweb项目部署到linux下

    下面是如何将Java Web项目部署到Linux下的完整攻略。 步骤一:准备工作 在将Java Web项目部署到Linux下之前,我们需要准备以下工具: 一台运行Linux操作系统的服务器 Java开发包(JDK) Tomcat服务器 Maven构建工具 Git版本控制工具 步骤二:编写代码并打包 在准备好工具之后,我们需要编写Java Web项目的代码并将…

    Java 2023年5月20日
    00
  • Java循环队列与非循环队列的区别总结

    Java循环队列与非循环队列的区别总结 什么是队列? 队列是计算机科学中一种常见的抽象数据类型,它代表了一组可以按顺序访问的元素,遵循 “先进先出” (FIFO) 的原则,也就是最先进入队列的元素最先被处理和弹出。 非循环队列 非循环队列是最普通的队列,也是最容易实现的。非循环队列采用静态数组或动态数组来实现。队列的读取位置(front) 和写入位置(rea…

    Java 2023年5月26日
    00
合作推广
合作推广
分享本页
返回顶部