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日

相关文章

  • java随机数生成具体实现代码

    当我们需要在程序中产生随机数时,Java API提供了几种不同的方法:Math类中的静态方法和java.util.Random类。 Math类生成随机数的实现代码 Math类中提供了一个random()方法来产生任意范围的随机数。通过random()方法返回一个0.0到1.0之间的随机数,对于大于1.0的范围,可以通过数学运算来实现。下面是一个产生1-100…

    Java 2023年5月23日
    00
  • java多线程CountDownLatch与线程池ThreadPoolExecutor/ExecutorService案例

    让我给您详细讲解一下关于Java多线程中CountDownLatch与线程池ThreadPoolExecutor/ExecutorService的用法及案例的完整攻略。这里会分为以下几个部分: 什么是CountDownLatch以及用途 CountDownLatch的用法示例 什么是线程池ThreadPoolExecutor/ExecutorService以…

    Java 2023年5月19日
    00
  • Java基础教程之组合(composition)

    Java基础教程之组合(Composition) 在Java中,组合是一种重要的关系类型。它允许我们在一个类中使用其他类的实例,从而简化代码并提高代码的可重用性。本文将详细介绍组合的概念及其在Java编程中的应用。 什么是组合 组合指的是一个类使用另外一个类的实例作为自己的一个字段,这个字段可以是一个单独的对象也可以是一个对象数组。组合的关系可以用一个UML…

    Java 2023年5月23日
    00
  • Java编程中的检查型异常与非检查型异常分析

    Java中的异常分为检查型异常和非检查型异常。检查型异常是指在编译期间就需要进行处理,否则代码将无法编译通过。非检查型异常则是指在运行期间发生,不处理也可以编译通过,但是会导致程序出错或崩溃。 检查型异常 检查型异常需要在程序中显式地进行处理。如果不处理,编译时就无法通过。常见的检查型异常有以下几种: IOException 当处理输入输出流时,由于设备或底…

    Java 2023年5月27日
    00
  • java获取用户输入的字符串方法

    下面我将为你详细讲解“java获取用户输入的字符串方法”的完整攻略。 一、使用Scanner类获取用户输入的字符串 在Java中,可以使用Scanner类来获取用户的输入。Scanner类提供了nextInt()、nextFloat()、nextBoolean()等方法,可以分别获取用户输入的整数、浮点数和布尔值。如果需要获取用户输入的字符串,可以使用Sca…

    Java 2023年5月26日
    00
  • 如何通过LambdaProbe实现监控Tomcat

    LambdaProbe是一种轻量级的Tomcat管理和监控工具,可以帮助我们更方便地查看Tomcat运行状态、性能指标和日志等信息。下面是通过LambdaProbe实现监控Tomcat的完整攻略,包含以下内容: 下载和安装LambdaProbe 配置Tomcat 启动Tomcat和LambdaProbe 使用LambdaProbe监控Tomcat 下载和安装…

    Java 2023年6月2日
    00
  • Spring Security内置过滤器的维护方法

    Spring Security 是一款基于 Servlet Filter 的安全框架,它提供了许多内置的过滤器来实现各种不同的安全策略。本文将详细讲解 Spring Security 内置过滤器的维护方法,以帮助开发者更好地使用 Spring Security。 什么是 Spring Security 内置过滤器? Spring Security 内置了许多…

    Java 2023年5月20日
    00
  • Springboot FatJa原理机制源码解析

    Springboot FatJar原理机制源码解析 什么是Springboot FatJar Springboot FatJar是一种打包方式,它将应用程序及其所有依赖库打包到一个可执行的JAR文件中。这样,我们只需要一个JAR文件就能部署整个应用程序到服务器上,而无需考虑依赖库的配置问题。同时,FatJar还具有开箱即用的特点,即使是在没有安装任何JDK或…

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