Java多数据源的三种实现方式小结

 更新时间:2025年03月31日 11:19:47   作者:黑尾土拨鼠  
多数据源是在一个应用程序中配置和使用多个不同的数据库连接,本文主要介绍了Java多数据源的三种实现方式小结,具有一定的参考价值,感兴趣的可以了解一下

1、什么时候会用到多数据源(Multiple Data Sources)

在Java开发中,“多数据源”指的是在一个应用程序中配置和使用多个不同的数据库连接。通常情况下,一个Java应用程序会连接到一个单一的数据库。然而,在一些复杂的应用场景中,可能需要访问多个不同的数据库,这时就需要配置多数据源。

1.1、多数据源具体含义

  • 多个数据库实例:多数据源可以是指同一个类型(如MySQL)的多个数据库实例。例如,一个应用程序可能需要同时连接到两个不同的MySQL数据库,一个用于存储用户信息,另一个用于存储订单信息。
  • 不同类型的数据库:多数据源也可以是指不同类型的数据库。例如,一个应用程序可能需要连接一个MySQL数据库来存储用户信息,同时还需要连接一个Oracle数据库来存储财务数据。
  • 不同的访问策略:多数据源配置也可能用于实现数据库访问策略的不同,比如读写分离(一个数据源用于写操作,另一个用于读操作),或者是在多租户架构下,不同租户使用不同的数据源。

1.2、为什么需要多数据源

  • 业务需求:不同的业务模块可能需要存储在不同的数据库中。例如,财务数据和用户数据可能分别存储在两个独立的数据库中,以便更好地进行管理和安全控制。
  • 系统架构:在大型分布式系统中,通常会使用分库分表来应对海量数据的挑战,不同的数据库实例可能分布在不同的服务器上。
  • 读写分离:为了提高系统的性能,尤其是在高并发场景下,常见的做法是将数据库的读操作和写操作分开,读操作可以从多个只读的从库(Slave)中获取数据,而写操作则写入到主库(Master)。
  • 迁移或兼容性:在系统迁移或升级的过程中,可能需要同时访问新旧两个数据库系统,或者需要兼容不同版本的数据库。
  • 多租户支持:在SaaS应用中,每个租户的数据可能被隔离存储在不同的数据库中,以确保数据的独立性和安全性。

2、Java中实现多数据源的三种方式

2.1 基于Spring提供的AbstractRoutingDataSource

使用 Spring 提供的 AbstractRoutingDataSource 实现多数据源配置是一种动态数据源管理的有效方法。通过继承 AbstractRoutingDataSource,可以根据某些条件(如请求上下文、当前线程信息等)动态地选择要使用的数据源。这种方式非常适合需要根据业务逻辑动态切换数据源的场景,如读写分离、分库分表等。
基于 AbstractRoutingDataSource 实现多数据源配置的详细步骤如下:

2.1.1 创建一个 DynamicDataSource 类

首先,创建一个继承 AbstractRoutingDataSource 的类,来实现数据源的动态切换逻辑。

@Component
@Primary
public class DynamicDataSource extends AbstractRoutingDataSource {
    // 当前使用的数据源标识
    public static ThreadLocal<String> name=new ThreadLocal<>();
    // 写数据源
    @Autowired
    DataSource dataSource1;
    // 读数据源
    @Autowired
    DataSource dataSource2;

    @Override
    protected Object determineCurrentLookupKey() {
        return name.get();
    }
    @Override
    public void afterPropertiesSet() {
        // 为targetDataSources初始化所有数据源
        Map<Object, Object> targetDataSources=new HashMap<>();
        targetDataSources.put("W",dataSource1);
        targetDataSources.put("R",dataSource2);

        super.setTargetDataSources(targetDataSources);

        // 为defaultTargetDataSource 设置默认的数据源
        super.setDefaultTargetDataSource(dataSource1);

        super.afterPropertiesSet();

    }

}

2.1.2 配置数据源

接下来,我们需要配置多个数据源。

@Configuration
public class DataSourceConfig {
	/**
		数据库1的配置
	/
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.datasource1")
    public DataSource dataSource1() {
        // 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSource
        return DruidDataSourceBuilder.create().build();
    }

	/**
		数据库2的配置
	/
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.datasource2")
    public DataSource dataSource2() {
        // 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSource
        return DruidDataSourceBuilder.create().build();
    }
    @Bean
    public DataSourceTransactionManager transactionManager1(DynamicDataSource dataSource){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }

    @Bean
    public DataSourceTransactionManager transactionManager2(DynamicDataSource dataSource){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}

2.1.3 自定义注解,便于区别不同数据源

在业务层,我们可以通过注解设置当前数据源的标识符来实现数据源的动态切换。

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

在业务层,我们可以通过注解设置当前数据源的标识符来实现数据源的动态切换。将自己实现的DynamicDataSource注册成为默认的DataSource实例后,只需要在每次使用DataSource时,提前改变一下其中的name标识,就可以快速切换数据源。

@Component
@Aspect
public class DynamicDataSourceAspect implements Ordered {
    // 前置
    @Before("within(org.arkham.dynamic_datasource.service.impl.*) && @annotation(wr)")
    public void before(JoinPoint point, WR wr){
        String name = wr.value();
        DynamicDataSource.name.set(name);
    }
    @Override
    public int getOrder() {
        return 0;
    }
}

2.1.4 Service通过注解设置数据源

在业务层具体使用方法上增加注解,实现数据源切换

@Service
public class UserImplService implements UserService {

    @Autowired
    UserMapper userMapper;


    @Override
    @WR("R")        // 库1
    public List<User> list() {
        return userMapper.list();
    }

    @Override
    @WR("W")        // 库2
    public void save(User friend) {
        userMapper.save(friend);
    }
}

2.2 使用MyBatis注册多个SqlSessionFactory

在Spring 环境中,如果有多个数据源并且需要为每个数据源配置单独的 SqlSessionFactory 和SqlSessionTemplate,可以通过配置多个 SqlSessionFactory 实现这一点。如果使用MyBatis框架,要注册多个数据源的话,就需要将MyBatis底层的DataSource、SqlSessionFactory、DataSourceTransactionManager这些核心对象一并进行手动注册

基于 MyBatis 注册多个 SqlSessionFactory实现多数据源配置的详细步骤如下:

2.2.1 不同数据源注册

数据源1:

@Configuration
// 继承mybatis:
// 1. 指定扫描的mapper接口包(主库)
// 2. 指定使用sqlSessionFactory是哪个(主库)
@MapperScan(basePackages = "org.arkham.dynamic_datasource_mybatis.mapper.w",
        sqlSessionFactoryRef="wSqlSessionFactory")
public class WMyBatisConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.datasource1")
    public DataSource dataSource1() {
        // 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSource
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public SqlSessionFactory wSqlSessionFactory()
            throws Exception {
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        // 指定主库
        sessionFactory.setDataSource(dataSource1());
        // 可以手动指定主库对应的mapper.xml文件
        /*sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/order/*.xml"));*/
        return sessionFactory.getObject();
    }

    @Bean
    @Primary
    public DataSourceTransactionManager wTransactionManager(){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource1());
        return dataSourceTransactionManager;
    }


    @Bean
    public TransactionTemplate wTransactionTemplate(){
        return new TransactionTemplate(wTransactionManager());
    }
}

数据源2:

@Configuration
// 继承mybatis:
// 1. 指定扫描的mapper接口包(主库)
// 2. 指定使用sqlSessionFactory是哪个(主库)
@MapperScan(basePackages = "org.arkham.dynamic_datasource_mybatis.mapper.r",
        sqlSessionFactoryRef="rSqlSessionFactory")
public class RMyBatisConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.datasource2")
    public DataSource dataSource2() {
        // 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSource
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public SqlSessionFactory rSqlSessionFactory()
            throws Exception {
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        // 指定主库
        sessionFactory.setDataSource(dataSource2());
        // 可以手动指定主库对应的mapper.xml文件
        /*sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/r/*.xml"));*/
        return sessionFactory.getObject();
    }



    @Bean
    public DataSourceTransactionManager rTransactionManager(){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource2());
        return dataSourceTransactionManager;
    }

    @Bean
    public TransactionTemplate rTransactionTemplate(){
        return new TransactionTemplate(rTransactionManager());
    }

2.2.2 业务层具体使用

在应用程序中,具体的 DAO 层使用 SqlSessionFactory 和 SqlSessionTemplate 时,MyBatis 会根据 @MapperScan 指定的包路径,使用相应的数据源。

@Service
public class UserImplService implements UserService {

    @Autowired
    private RUserMapper rFriendMapper;

    @Autowired
    private WUserMapper wFriendMapper;
    // 读-- 读库
    @Override
    public List<User> list() {
        return rFriendMapper.list();
    }

    // 保存-- 写库
    @Override
    public void save(User user) {
        wFriendMapper.save(user);
    }


    // 保存-- 写库
    @Override
    public void saveW(User user) {
        user.setName("xman11");
        wFriendMapper.save(user);
    }

    // 保存-- 读库
    @Override
    public void saveR(User user) {
        user.setName("xman");
        rFriendMapper.save(user);
    }
}

2.3 使用dynamic-datasource框架

使用 dynamic-datasource-spring-boot-starter 框架可以非常方便地实现 Java 项目中的多数据源配置。这个框架能够支持多种数据源的自动切换、动态数据源的配置管理等功能。
基于 dynamic-datasource-spring-boot-starter 框架实现多数据源配置的详细步骤如下:

2.3.1 添加依赖

首先,需要在 pom.xml 文件中添加 dynamic-datasource-spring-boot-starter 的依赖。

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.5.0</version> <!-- 请根据项目需要调整版本 -->
</dependency>

2.3.2 配置数据源

在application.yml 文件中配置多个数据源。可以给每个数据源起一个唯一的名称(例如 master 和 slave)

spring:
  datasource:
    dynamic:
      primary: master # 默认数据源或数据源组,当未指定数据源时会使用该数据源
      strict: false # 设置为true表示检查配置文件中数据源是否有效
      datasource:
        master: # 数据源一
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/master_db
          username: master_user
          password: master_pass
        slave: # 数据源二
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/slave_db
          username: slave_user
          password: slave_pass

2.3.3 配置数据源

使用 @DS 注解来指定某个方法或类使用特定的数据源。

@Service
public class UserImplService implements UserService {

    @Autowired
    UserMapper userMapper;

    @Override
    @DS("master")
    public void save(User user) {
        userMapper.save(user);
    }
    @Override
    @DS("slave_1")  // 从库, 如果按照下划线命名方式配置多个 , 可以指定前缀即可(组名)
    public List<User> list() {
        return userMapper.list();
    }
}

对于 MyBatis,dynamic-datasource 同样适用。只需正常配置 MyBatis,并在 Mapper 方法上使用 @DS 注解即可。

import com.baomidou.dynamic.datasource.annotation.DS;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface UserMapper {

    @DS("master")
    @Select("SELECT * FROM user WHERE id = #{id}")
    User getUserFromMaster(int id);

    @DS("slave")
    @Select("SELECT * FROM user WHERE id = #{id}")
    User getUserFromSlave(int id);
}

dynamic-datasource 框架提供了一种简洁、高效的方式来实现多数据源的配置和动态切换。通过注解方式,可以轻松切换数据源,满足业务中对多数据源的需求。

3 总结

这三种实现多数据源的方式各有优缺点,适用于不同的场景,具体选择哪种方式还需根据实际需求来判断。此外,配置多数据源的方式不止于此,例如在使用 Spring Data JPA 时,也可以通过配置多个 EntityManager 实现多数据源。每个 EntityManager 可以与特定的 DataSource 关联,从而实现对多个数据库的操作。因此,在多数据源的配置中,灵活选择适合的方案至关重要。

源码地址:https://github.com/arkhamYJ/datasource.git

到此这篇关于Java多数据源的三种实现方式小结的文章就介绍到这了,更多相关Java多数据源内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 深入理解Java中没那么简单的单例模式

    深入理解Java中没那么简单的单例模式

    这篇文章主要给大家详细介绍了Java单例模式,关于Java中的单例模式并非看起来那么简单的,为什么要这么说呢?下面通过这篇文章来一起看看吧,有需要的朋友们可以参考借鉴。
    2017-01-01
  • Java并发编程中的生产者与消费者模型简述

    Java并发编程中的生产者与消费者模型简述

    这篇文章主要介绍了Java并发编程中的生产者与消费者模型简述,多线程并发是Java编程中最终要的部分之一,需要的朋友可以参考下
    2015-07-07
  • Java服务调用失败报Service com.oneinfinite.adflow.api.service.TestService未找到的解决方法

    Java服务调用失败报Service com.oneinfinite.adflow.api.service.T

    在Java开发中,服务调用是常见的操作,尤其是在微服务架构中,然而,服务调用过程中可能会遇到各种问题,下面我们来看看如何解决Service com.oneinfinite.adflow.api.service.TestService with version 0.0.0 not found的问题吧
    2025-03-03
  • java项目实现猜拳小游戏

    java项目实现猜拳小游戏

    这篇文章主要为大家详细介绍了java项目实现猜拳小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-05-05
  • SpringBoot中项目如何读取外置logback配置文件

    SpringBoot中项目如何读取外置logback配置文件

    这篇文章主要介绍了SpringBoot中项目如何读取外置logback配置文件问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11
  • Java 8中Stream API的这些奇技淫巧!你Get了吗?

    Java 8中Stream API的这些奇技淫巧!你Get了吗?

    这篇文章主要介绍了Java 8中Stream API的这些奇技淫巧!你Get了吗?文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-08-08
  • JAVA的Dubbo如何实现各种限流算法

    JAVA的Dubbo如何实现各种限流算法

    Dubbo是一种高性能的Java RPC框架,广泛应用于分布式服务架构中,在Dubbo中实现限流可以帮助服务在高并发场景下保持稳定性和可靠性,常见的限流算法包括固定窗口算法、滑动窗口算法、令牌桶算法和漏桶算法,在Dubbo中集成限流器可以通过实现自定义过滤器来实现
    2025-01-01
  • Java中throw和throws异常处理完整例子说明

    Java中throw和throws异常处理完整例子说明

    这篇文章主要给大家介绍了关于Java中throw和throws异常处理的相关资料, throw关键字是用于在方法内抛出异常,而throws关键字是在方法声明中指定可能抛出的异常,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-06-06
  • 如何使用Guava Cache做缓存

    如何使用Guava Cache做缓存

    Cache在ConcurrentHashMap的基础上提供了自动加载数据、清除数据、get-if-absend-compute的功能,本文给大家介绍如何使用Guava Cache做缓存,感兴趣的朋友一起看看吧
    2023-11-11
  • Java创建可执行JAR文件的多种方式

    Java创建可执行JAR文件的多种方式

    本文主要介绍了Java创建可执行JAR文件的多种方式,使用JDK的jar工具、IDE、Maven和Gradle来创建和配置可执行JAR文件,具有一定的参考价值,感兴趣的可以了解一下
    2024-07-07

最新评论