首页 > 其他分享 >SpringBoot多数据源开发

SpringBoot多数据源开发

时间:2024-11-22 18:44:45浏览次数:3  
标签:SpringBoot snail 数据源 开发 线程 import com public

前言

在企业级开发中,多数据源是一种常见的技术方案。在面对复杂的业务场景时,通常会对数据库进行横向和纵向的拆分。横向拆分如读写分离,通过主从复制的方式减轻主库的读压力;纵向拆分则是按模块拆分数据库,提升单库性能。 在Spring Boot项目中,怎么实现多数据源支持?一起通过案例解析,探索下数据源的应用。

一、案例

1.1配置文件

  • 配置两个druid数据源,作为主从库
  • “master”、"slave"自定义,主要是在配置类中进行区分,以注入不同的数据源属性
spring:
  datasource:
    druid:
      master:
        url: jdbc:mysql://*.*.*.*:3306/snail_db
        username: lazysnail
        password: ******
        driver-class-name: com.mysql.cj.jdbc.Driver
      slave:
        url: jdbc:mysql://*.*.*.*:3306/snail_db_slave
        username: lazysnail
        password: ******
        driver-class-name: com.mysql.cj.jdbc.Driver

1.2动态数据源配置

  • 定义动态数据源DynamicRoutingDataSource,所有的数据库操作会通过此动态数据源Bean路由到正确的目标数据源。

  • 通过@ConfigurationProperties将以spring.datasource.druid.master为前缀的配置绑定到DruidDataSource属性,定义主数据源 DataSource的bean

  • 通过@ConfigurationProperties将以spring.datasource.druid.slave为前缀的配置绑定到DruidDataSource属性,定义从数据源DataSource的bean

  • 定义MyBatis的SqlSessionFactory,负责创建和管理与数据库交互的SqlSession实例。

  • 定义MyBatis的SqlSessionTemplate,SqlSessionTemplate是Mapper层与数据库交互的桥梁。

package com.lazy.snail.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.lazy.snail.datasource.DynamicRoutingDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName DynamicDataSourceConfig
 * @Description TODO
 * @Author lazysnail
 * @Date 2024/11/18 14:24
 * @Version 1.0
 */
@Configuration
public class DynamicDataSourceConfig {

    @Bean
    public DataSource dynamicDataSource(
            @Qualifier("masterDataSource") DataSource masterDataSource,
            @Qualifier("slaveDataSource") DataSource slaveDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", masterDataSource);
        targetDataSources.put("slave", slaveDataSource);

        // 默认使用主数据源
        DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource();
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
        dynamicDataSource.setTargetDataSources(targetDataSources);
        return dynamicDataSource;
    }

    @Bean(name = "masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.druid.master")
    public DataSource masterDataSource() {
        return new DruidDataSource();
    }

    @Bean(name = "slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.druid.slave")
    public DataSource slaveDataSource() {
        return new DruidDataSource();
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dynamicDataSource);
        //factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml")); // 根据实际情况调整路径
        return factoryBean.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

1.3动态路由

  • 继承自AbstractRoutingDataSource,实现 Spring 的动态数据源路由功能。
  • determineCurrentLookupKey是AbstractRoutingDataSource的核心方法,用于决定当前线程需要使用哪个数据源。
  • 通过DynamicDataSourceContextHolder获取上下文中指定的数据源标识,实现主从库切换。
package com.lazy.snail.datasource;

import com.lazy.snail.holder.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * @ClassName DynamicRoutingDataSource
 * @Description TODO
 * @Author lazysnail
 * @Date 2024/11/18 14:35
 * @Version 1.0
 */
@Slf4j
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        String currentDataSource = DynamicDataSourceContextHolder.getDataSourceKey();
        log.info("Current DataSource is: {}", currentDataSource);
        return currentDataSource;
    }
}


  • 使用ThreadLocal实现数据源的动态上下文管理。
    1. ThreadLocal是一个线程安全的工具,确保每个线程都有独立的上下文变量副本。
    2. 这里的ThreadLocal用于存储每个线程当前使用的数据源标识(如 “master"或"slave”)。
  • setDataSourceKey用于设置当前线程的数据源标识,一般在切换数据源(如进入@Master或@Slave注解的方法)时调用。
  • getDataSourceKey获取当前线程的数据源标识,DynamicRoutingDataSource.determineCurrentLookupKey()方法调用这个方法,动态决定当前目标数据源。
  • clearDataSourceKey清除当前线程的数据源标识,一般在数据源切换完成后,防止上下文污染其他操作。
package com.lazy.snail.holder;

/**
 * @ClassName DynamicDataSourceContextHolder
 * @Description TODO
 * @Author lazysnail
 * @Date 2024/11/18 14:30
 * @Version 1.0
 */
public class DynamicDataSourceContextHolder {
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    public static void setDataSourceKey(String key) {
        CONTEXT_HOLDER.set(key);
    }

    public static String getDataSourceKey() {
        return CONTEXT_HOLDER.get();
    }

    public static void clearDataSourceKey() {
        CONTEXT_HOLDER.remove();
    }
}

1.4主从注解

  • 用于标记主从库数据源
  • 在需要写操作(如INSERT、UPDATE、DELETE)或对数据一致性要求高的场景下,方法上标记@Master,确保这些操作始终在主库执行。
  • 在只需要读操作(如SELECT)的场景下,方法上标记@Slave,将查询请求路由到从库,减轻主库压力,提高读写分离性能。
package com.lazy.snail.annotation;

/**
 * @ClassName Master
 * @Description TODO
 * @Author lazysnail
 * @Date 2024/11/18 14:28
 * @Version 1.0
 */
import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Master {
}

package com.lazy.snail.annotation;

import java.lang.annotation.*;

/**
 * @ClassName Slave
 * @Description TODO
 * @Author lazysnail
 * @Date 2024/11/18 14:28
 * @Version 1.0
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Slave {
}

1.5数据源切面

  • DataSourceAspect类通过拦截被标记为@Master或@Slave的方法,动态地设置当前线程使用的数据库连接(主库或从库)。
    1. 定义切入点
      • @Pointcut注解定义了切入点,即标记了哪些方法需要动态切换数据源。
      • masterPointCut():匹配所有使用@Master注解的方法。
      • slavePointCut():匹配所有使用@Slave注解的方法。
    2. 动态数据源切换逻辑
      • @Around注解包裹方法执行,通过拦截方法调用,动态调整数据源。
      • 在方法执行前,设置当前线程的数据源为主库或从库。
      • 在方法执行完成后,无论是否发生异常,都清除线程中的数据源标识,防止后续线程调用时使用错误的数据源。
    3. 线程安全性
      • 通过DynamicDataSourceContextHolder类的ThreadLocal存储数据源信息,保证每个线程的数据源选择互不干扰。
package com.lazy.snail.aspect;

import com.lazy.snail.holder.DynamicDataSourceContextHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * @ClassName DataSourceAspect
 * @Description TODO
 * @Author lazysnail
 * @Date 2024/11/18 14:25
 * @Version 1.0
 */
@Aspect
@Component
public class DataSourceAspect {

    @Pointcut("@annotation(com.lazy.snail.annotation.Master)")
    public void masterPointCut() {}

    @Pointcut("@annotation(com.lazy.snail.annotation.Slave)")
    public void slavePointCut() {}

    @Around("masterPointCut()")
    public Object useMaster(ProceedingJoinPoint joinPoint) throws Throwable {
        DynamicDataSourceContextHolder.setDataSourceKey("master");
        try {
            return joinPoint.proceed();
        } finally {
            DynamicDataSourceContextHolder.clearDataSourceKey();
        }
    }

    @Around("slavePointCut()")
    public Object useSlave(ProceedingJoinPoint joinPoint) throws Throwable {
        DynamicDataSourceContextHolder.setDataSourceKey("slave");
        try {
            return joinPoint.proceed();
        } finally {
            DynamicDataSourceContextHolder.clearDataSourceKey();
        }
    }
}

1.6启动类修改

  • 此处去掉了两个自动配置类,DataSourceAutoConfiguration和DruidDataSourceAutoConfigure
  • SpringBoot启动时,如果存在spring-boot-starter-jdbc或spring-boot-starter-data-jpa,则会使用DataSourceAutoConfiguration自动配置数据源。
  • 如果存在druid-spring-boot-starter,则会通过DruidDataSourceAutoConfigure尝试创建数据源。
  • 我们的多数据源是通过手动配置,然后动态路由实现的。
  • 如果不排除上述数据源的自动配置功能可能会出现问题
    1. SpringBoot尝试创建默认数据源,而我们已经手动配置了动态数据源,可能导致容器中存在多个数据源的bean。如果没有明确指定使用哪个数据源,会抛出异常。
    2. 如果再配置文件中没有完整的数据源配置(spring.datasource.url)供自动配置使用,会因为缺少参数抛出异常。
    3. SpringBoot默认的数据源逻辑可能与动态数据源切换逻辑相互干扰,导致切换功能失效。
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DruidDataSourceAutoConfigure.class})

1.7客户端调用

  • 通过在方法上使用@Master或者@Slave注解,指定方法使用哪个数据源
package com.lazy.snail.mapper;

import com.lazy.snail.annotation.Master;
import com.lazy.snail.annotation.Slave;
import com.lazy.snail.dimain.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface UserMapper {
    @Master
    @Select("select * from user_info where user_id=#{id}")
    User selectById(int id);

    @Select("select * from user_info")
    @Slave
    List<User> selectAll();
}
  • 调用结果,从控制台可以直观的看出执行selectById和selectAll方法时,数据源的切换动作。

二、核心原理

2.1动态数据源路由

  • 核心机制基于Spring提供的AbstractRoutingDataSource。
  • 动态路由的关键在于重写其determineCurrentLookupKey方法,根据上下文决定当前使用的数据源。

2.2数据源上下文隔离

  • 利用ThreadLocal维护每个线程独立的数据源标识,保证线程间的数据源设置互不干扰。

2.3动态切换的数据源映射

  • 通过配置一个路由数据源,将多个实际数据源注册到路由映射表中。
  • 默认数据源用于应对没有明确标识时的访问。

2.4方法级数据源切换

  • 利用Spring AOP实现方法级别的数据源切换:
    • 在方法调用前,通过注解动态设置线程上下文中的数据源标识。
    • 方法执行后清理上下文,避免数据源污染。

三、核心技术

3.1数据源Bean管理

  • 通过手动配置多个数据源Bean,并利用动态路由管理器将其映射到对应的标识。

3.2注解驱动

  • 自定义注解(如@Master和@Slave),结合AOP切面实现方法级别的切换逻辑。

3.3配置剥离

  • 禁用默认的自动数据源配置(如DataSourceAutoConfiguration),手动管理数据源注册与加载,增强灵活性。

3.4Spring AOP

  • 切面用于拦截注解方法,在方法执行前后动态调整线程上下文中的数据源设置。

3.5线程安全保障

  • 使用ThreadLocal实现线程级的数据源标识存储,保证多线程环境下的安全切换。

3.6IoC动态绑定

  • 利用Spring IoC容器的依赖注入能力,将动态路由数据源注册为全局数据源,使其对业务代码透明。

四、适用场景

  • 读写分离:主库负责写操作,从库负责读操作,参考本案例。

  • 多租户系统:根据租户信息动态切换不同的数据源。

  • 数据分片:根据业务逻辑选择特定的数据库。

标签:SpringBoot,snail,数据源,开发,线程,import,com,public
From: https://blog.csdn.net/indolentSnail/article/details/143897373

相关文章

  • 游戏开发入门 | 教新手小白捋清游戏PV的思路
    游戏PV:指游戏发行时所制作的同步宣传影像。为什么一个好的游戏PV如此重要?“会做游戏不就好了?”“为什么还要学做游戏PV?”对于游戏开发者来说,游戏的玩法、画面和数值等要素往往值得更多关注。然而,若是宣发乏力,无论是设计多么精妙的游戏都有遭埋没之险。那些新鲜问世的游......
  • 鸿蒙NEXT开发案例:数字统计
     【引言】本文将通过一个具体的案例——“数字统计”组件,来探讨如何在鸿蒙NEXT框架下实现这一功能。此组件不仅能够统计用户输入文本中的汉字、中文标点、数字、以及英文字符的数量,还具有良好的用户界面设计,使用户能够直观地了解输入文本的各种统计数据。【环境准备】•操......
  • JNPF低代码开发平台赋能数智化转型探索及趋势分析
    引言随着信息技术的飞速发展,企业面临的市场竞争日益激烈。数智化转型成为企业提升核心竞争力、实现可持续发展的关键路径。在此背景下,低代码开发平台应运而生,其中JNPF低代码开发平台以其独特的优势,成为推动企业数智化转型的重要工具。本文将探讨JNPF低代码开发平台如何赋能企......
  • android开发中,button设置shape后,shape的颜色不生效的问题解决方案
    检查AndroidManifest.xml中的主题的属性<applicationandroid:name=".BaseApplication"android:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:networkSecurityConf......
  • 【Linux工作记录】 grafana面板添加clickhouse数据源
    登录grafana的ui界面中添加clickhouse数据源发现没有找到clickhouse数据源操作步骤:1、到grafana节点机器中,找到grafana的bin目录2、安装clickhouse数据源插件./grafanaclipluginsinstallgrafana-clickhouse-datasourceError:✗pluginsDir(/var/lib/grafana/plugins)......
  • 【附源码】springboot贫困地区儿童资助系统设计与实现
    博主介绍:✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌技术范围:SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数......
  • 【附源码】springboot 企业人才引进服务平台设计与实现
    博主介绍:✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌技术范围:SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数......
  • 【附源码】springboot 校园导航微信小程序设计与实现
    博主介绍:✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌技术范围:SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数......
  • 零基础同时入门并掌握C语言和C++——第一节——选择开发环境
    本系列文章将针对C语言使用VisualStudio2022, C++使用DevC++作为开发环境进行讲解。下面分别讲述选择这两款开发环境的原因和好处:DevC++市面上有很多版本,常见的有蓝色(也就是图片中展示的这款)红色,和小熊猫等。对于初学者来说可能会纠结究竟下载哪款才正确和会不会下载到盗版......
  • 【附源码】springboot 网上订餐系统设计与实现
    博主介绍:✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌技术范围:SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数......