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日

相关文章

  • 微信小程序 springboot后台如何获取用户的openid

    首先,我们需要了解一下微信小程序的认证流程和openid的概念。 在用户使用微信小程序时,小程序会向微信服务器发送登录请求,微信服务器会返回给小程序一个特有的code参数。小程序前端拿到这个code参数后,需要发送一个HTTP请求到我们的后台服务器,我们的后台服务器再使用这个code参数向微信服务器发送请求,获取用户的openid。 openid是微信中用于…

    Java 2023年5月23日
    00
  • HttpClient实现文件上传功能

    以下是关于HttpClient实现文件上传功能的完整攻略。 简介 HttpClient是Apache的一个开源组件,它提供了高效的、简单的、简洁的编程接口,用于发送HTTP/HTTPS请求并处理响应。支持字符集转换、错误处理、重试处理、SSL连接、连接池等。 文件上传是HTTP协议中常用的一个功能,在web开发中尤为常见。HttpClient提供了完整的封装…

    Java 2023年6月15日
    00
  • 浅谈SpringMVC jsp前台获取参数的方式 EL表达式

    关于浅谈SpringMVC jsp前台获取参数的方式 EL表达式,以下是完整攻略。 一、什么是EL表达式 EL(Expression Language)表达式是JSP 2.0引入的一种表达式语言,它主要用于动态的访问和操作JavaBean中的数据。 二、EL表达式的特点 EL表达式有以下特点: 提供了一种简洁的访问JavaBean属性的方式,不需要借助Jav…

    Java 2023年6月15日
    00
  • Java将文件夹保留目录打包为 ZIP 压缩包并下载的教程详解

    下面是关于“Java将文件夹保留目录打包为 ZIP 压缩包并下载的教程详解”的完整攻略。 前言 在Java程序中,我们有时会需要将一个文件夹以及其中的文件打包成ZIP格式的压缩文件并下载。本文将介绍如何实现这个功能。 代码实现 Java提供了ZipOutputStream类和ZipEntry类,可以轻松地打包一个文件夹中的所有文件并生成ZIP文件。我们可以使…

    Java 2023年5月19日
    00
  • 解决Spring Security中AuthenticationEntryPoint不生效相关问题

    解决Spring Security中AuthenticationEntryPoint不生效相关问题,主要有以下几个步骤: 确认AuthenticationEntryPoint是否配置正确 在Spring Security配置文件中,需要配置AuthenticationEntryPoint,用来处理认证失败后的跳转或返回错误信息。一些常见的Authentica…

    Java 2023年5月20日
    00
  • 一文掌握IDEA中的Maven集成与创建

    下面我将详细讲解“一文掌握IDEA中的Maven集成与创建”的完整攻略。 IDEA中Maven集成 第一步:安装Maven 打开IDEA,选择Preferences,然后在搜索框中输入Maven,找到Maven设置选项,查看当前是否已经安装了Maven,如果没有,请点击“+”来安装Maven。 第二步:创建Maven项目 在IDEA中选择“新建项目” -&g…

    Java 2023年5月20日
    00
  • JavaWeb登录界面登录失败在同一页面进行提示的解决

    JavaWeb登录界面登录失败在同一页面进行提示的解决 当用户在JavaWeb应用程序中的登录界面输入错误的用户名或密码时,我们需要给与用户提示以完成用户友好体验。该过程有多种方法可以完成,其中一种方法是在同一页面上进行提示。本文将讲解如何在同一页面上显示登录失败的提示信息。 第一步:页面设计我们需要在登录页面添加一个div元素,将错误信息放在里面。但是在一…

    Java 2023年6月15日
    00
  • 利用solr实现商品的搜索功能(实例讲解)

    以下是利用Solr实现商品的搜索功能的完整攻略: 准备工作 安装Java环境和Solr 导入商品数据到Solr中 创建schema和field定义 在Solr中创建schema.xml文件,并定义field: <field name="id" type="string" indexed="true&qu…

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