Spring配置多数据源切换

yizhihongxing

下面我将详细讲解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日

相关文章

  • 解决fastjson泛型转换报错的解决方法

    解决fastjson泛型转换报错的解决方法 问题描述: fastjson是Java中一个非常常用的JSON处理库,其中序列化和反序列化功能特别强大,但在使用其进行泛型反序列化时,会出现“com.alibaba.fastjson.JSONException: parse error”等异常,这就涉及到fastjson泛型转换错误的问题。 解决方法: 解决这个问…

    Java 2023年5月26日
    00
  • 关于Java垃圾回收开销降低的几条建议

    关于Java垃圾回收开销降低的几条建议 背景 在Java程序运行时,垃圾回收器自动地回收未被引用的内存,以免Java运行时内存不足。然而,频繁的垃圾回收和内存分配会增加系统的开销。因此,为了降低Java垃圾回收开销,我们可以采取以下几个建议: 建议一:减少内存分配 内存分配是Java运行时系统的开销之一。我们可以采取以下方法来减少内存分配: String处理…

    Java 2023年5月27日
    00
  • MyBatisPlus TypeHandler自定义字段类型转换Handler

    下面是”MyBatisPlus TypeHandler自定义字段类型转换Handler”的完整攻略: 什么是 MyBatisPlus TypeHandler MyBatisPlus TypeHandler是MyBatis用于处理Java的JDBC类型与数据库的JDBC类型相互转换的接口。 MyBatisPlus为我们提供了很多预定义的TypeHandler,…

    Java 2023年5月20日
    00
  • 如何使用Reactor完成类似Flink的操作

    使用Reactor完成类似Flink的操作可以分为以下几个步骤: 创建Flux或Mono:首先需要创建Flux或Mono,Flux表示可以产生多个数据流,Mono表示只能产生一个数据流; 转换Flux或Mono:可以使用map()、flatMap()、filter()等函数对Flux或Mono进行转换,获得想要的结果; 订阅Flux或Mono:最后需要订阅F…

    Java 2023年5月20日
    00
  • Java连接Oracle数据库并查询

    下面将详细讲解“Java连接Oracle数据库并查询”的完整攻略。 准备工作 在连接Oracle数据库之前,我们需要完成以下几项准备工作: 确保已经安装了 Java Runtime Environment(JRE),并设置了系统环境变量。 下载并安装 Oracle 数据库。 启动 Oracle 数据库,并创建一个测试用户。我们假设该用户的用户名为 testu…

    Java 2023年5月19日
    00
  • Java实战玩具商城的前台与后台实现流程

    Java实战玩具商城的前台与后台实现流程 概述 Java实战玩具商城的前台与后台实现流程主要分为以下几步: 前端页面设计:设计商城的页面布局和逻辑,并使用HTML、CSS和JavaScript等技术实现页面的交互效果。 后台架构设计:设计商城的后台架构,包括实现分布式服务、数据库设计、接口设计等。 业务逻辑实现:根据商城运营需求,实现各项业务逻辑,包括商品管…

    Java 2023年5月26日
    00
  • indexedDB bootstrap angularjs之 MVC DOMO (应用示例)

    “indexedDB bootstrap angularjs之 MVC DOMO (应用示例)”指的是使用indexedDB、bootstrap和angularjs构建MVC模式的web应用程序的示例。以下是详细的攻略: 1. 环境准备 安装Node.js和npm; 安装bower:npm install -g bower; 创建一个新的文件夹,并通过终端进…

    Java 2023年6月15日
    00
  • C#实现的最短路径分析

    下面是C#实现最短路径分析的完整攻略: 什么是最短路径分析? 最短路径分析是图论中的一个重要问题,在某个图中,起点到终点之间有多条路径可以选择,最短路径算法就是找到这些路径中最短的那个。最短路径算法可应用于交通运输、电信网络等众多领域中。 最短路径分析的算法及实现 最短路径分析的算法有多种,其中 Dijkstra 算法和 Floyd-Warshall 算法较…

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