SpringBoot多数据源

news/2024/5/19 0:08:23/文章来源:https://blog.csdn.net/qq_40366738/article/details/126862345

使用场景

在实际开发中,可能遇到多数据源的场景。

  1. 业务复杂(数据量大)
    数据分布在不同的数据库中,对业务数据进行垂直拆分。
    可以拆分为微服务架构,依赖的业务可以通过远程调用的方式来是实现,那么这种方式是不存在多数据源的情况。但是对于性能要求不高,以实现功能为目的,则可以使用多数据源。
    在这里插入图片描述
  2. 读写分离
    为了解决数据库读性能瓶颈(读比写的性能要高,但是写锁会影响阻塞,从而影响读性能)
    主从架构:一台主数据库对外提供增删改业务,其他从数据库进行读操作。
    在这里插入图片描述
  3. 这里还有读取上传的业务。

自定义多数据源实现类

在这里插入图片描述
在这里插入图片描述

关键类解析

DynamicDataSource 需要编写这个类并继承 AbstractRoutingDataSource

/*** 继承抽象路由数据源类AbstractRoutingDataSource*/
public class DynamicDataSource extends AbstractRoutingDataSource {/*** 每次查询数据库都会执行该方法* @return*/@Overrideprotected Object determineCurrentLookupKey() {// 返回AbstractRoutingDataSource类targetDataSources集合的key。return null;}
}

AbstractRoutingDataSource解析

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {@Nullableprivate Map<Object, Object> targetDataSources;@Nullableprivate Object defaultTargetDataSource;private boolean lenientFallback = true;private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();@Nullableprivate Map<Object, DataSource> resolvedDataSources;@Nullableprivate DataSource resolvedDefaultDataSource;// 这个类中有四个需要传入的非空属性。其中我们只需要传入上面两个即可,下面的两个会通过调用默认的afterPropertiesSet()方法来赋值。public void afterPropertiesSet() {if (this.targetDataSources == null) {throw new IllegalArgumentException("Property 'targetDataSources' is required");} else {this.resolvedDataSources = new HashMap(this.targetDataSources.size());this.targetDataSources.forEach((key, value) -> {Object lookupKey = this.resolveSpecifiedLookupKey(key);DataSource dataSource = this.resolveSpecifiedDataSource(value);this.resolvedDataSources.put(lookupKey, dataSource);});if (this.defaultTargetDataSource != null) {this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);}}// 这里省略
}

示例:

  1. 创建SpringBoot程序,并编写配置文件。配置好两个数据源。这里我的叫db1,db2
server:port: 8888
spring:application:name: MultiDatasourcedatasource:driver-class-name: com.mysql.cj.jdbc.Drivername: defaultDataSourceurl: jdbc:mysql://localhost:3306/blue?serverTimezone=UTCusername: 'root'password: '123456'type: com.zaxxer.hikari.HikariDataSourcedb1:url: jdbc:mysql://localhost:3306/blue?serverTimezone=UTCusername: 'root'password: '123456'db2:url: jdbc:mysql://localhost:3306/blue?serverTimezone=UTCusername: 'root'password: '123456'mybatis:mapper-locations: classpath:mappers/*xmltype-aliases-package: com.hx.multiDB.entityconfiguration.map-underscore-to-camel-case: true
  1. 新建数据源配置类 DataSourceConfig.java(包含了数据源配置)
package com.hx.multiDB.config;import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;@Configuration
public class DataSourceConfig {@Bean(name = "db1")@ConfigurationProperties("spring.datasource.db1")public DataSource db1DataSource() {return DataSourceBuilder.create().build();}@Bean@ConfigurationProperties("spring.datasource.db2")// 配置当存在enable属性值,且为true时才注入容器@ConditionalOnProperty(prefix = "spring.datasource.db2",name = "enable",havingValue = "true")public DataSource db2() {return DataSourceBuilder.create().build();}/*** 通过AOP在不同的数据库之间进行切换。** @param db1* @return*/@Primary    //告诉spring这个是主数据源@Bean// 注意这个类名不可以public DynamicDataSource dynamicDB(@Qualifier("db1") DataSource db1) {DynamicDataSource dynamicDataSource = new DynamicDataSource();Map<Object, Object> targetDataSources = new HashMap<>();// 这里有两种方法把上面的配置放入map集合中,1.以参数传入,2.调用方法targetDataSources.put("db1", db1);targetDataSources.put("db2", db2());dynamicDataSource.setDefaultTargetDataSource(db1);dynamicDataSource.setTargetDataSources(targetDataSources);dynamicDataSource.afterPropertiesSet();return dynamicDataSource;}
}
  1. 新建动态数据源类 DynamicDataSource.java(继承AbstractRoutingDataSource)
package com.hx.multiDB.config;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;import java.util.Map;/*** 继承抽象路由数据源类AbstractRoutingDataSource* 这里使用的是AbstractRoutingDataSource来切换配置,* 我们也可以使用mybatis中的SqlSessionTemplate,* 继承他的getSqlSessionFactory来切换数据源*/
public class DynamicDataSource extends AbstractRoutingDataSource {/*** 每次查询数据库都会执行该方法** @return*/@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceHandler.getDB();}
}
  1. 创建动态数据源处理类(包括保存数据源的容器,设置、获取、清空数据源方法)
package com.hx.multiDB.config;import lombok.extern.slf4j.Slf4j;/*** 动态数据源处理器*/
@Slf4j
public class DynamicDataSourceHandler {// 必须要线程变量的集合。不能用线程不安全的,否则在查询的时候并发切换,会导致错误。private static final ThreadLocal<String> DATASOURCE_HOLDER = new ThreadLocal<>();public static void setDB(String dbName){log.info("切换数据源为:{}",dbName);DATASOURCE_HOLDER.set(dbName);}public static String getDB(){return DATASOURCE_HOLDER.get();}// 每次使用完毕都要移除,否则会导致内存泄露public static void clear(){DATASOURCE_HOLDER.remove();}
}

利用Aop和注解来自动切换数据源

  1. 新建数据源注解 DataSource.java
package com.hx.multiDB.config;import java.lang.annotation.*;@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {String value();
}
  1. 创建数据源AOP DataSourceAop.java
package com.hx.multiDB.config;import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.Joinpoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;import java.lang.annotation.Annotation;
import java.lang.reflect.Method;@Aspect
@Component
@Slf4j
public class DataSourceAop {// 通过参数来拿到注解
//    @Around("@annotation(dataSource)")
//    public Object around( DataSource dataSource){
//        dataSource.value();
//    }// 通过切点对象来获取注解
//    @Around("@annotation(com.hx.multiDB.config.DataSource)")// 切dao中的所有方法。@Around("execution(* com.hx.multiDB.mapper.*Mapper*.*(..))")public Object around(ProceedingJoinPoint joinpoint) {log.info("进入环绕通知...");MethodSignature signature = (MethodSignature) joinpoint.getSignature();Method method = signature.getMethod();// 获取方法上的注解DataSource annotation = method.getAnnotation(DataSource.class);if(annotation == null){// 通过签名拿到类Class type = signature.getDeclaringType();// 拿到类上的注解Annotation methodAnno = type.getAnnotation(DataSource.class);if(methodAnno instanceof DataSource){annotation = (DataSource) methodAnno;}}if (annotation != null) {String value = annotation.value();// 设置数据源DynamicDataSourceHandler.setDB(value);}try {return joinpoint.proceed();} catch (Throwable throwable) {throwable.printStackTrace();return null;}finally {// 清空,避免内存泄露DynamicDataSourceHandler.clear();log.info("清空数据源Holder...");}}
}

测试

  1. 编写TestMapper1.java和TestMapper2.java
/*** 实际开发可能是读写分离等场景。*/
@Mapper
@Repository
public interface TestMapper1 {@Select(" SELECT * FROM TEST")List<Test> getAll();
}
/***** 以下是TestMapper2.java *****/
@Mapper
@Repository
public interface TestMapper2 {@Select(" SELECT * FROM TEST2 ")List<Test> getAll();
}
  1. 新建测试 TestController.java
package com.hx.multiDB.controller;import com.hx.multiDB.config.DataSource;
import com.hx.multiDB.mapper.TestMapper1;
import com.hx.multiDB.mapper.TestMapper2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping(path="/test",method={RequestMethod.GET,RequestMethod.POST})
public class TestController {@Autowiredprivate TestMapper1 mapper1;@Autowiredprivate TestMapper2 mapper2;@GetMapping("/select1")public Object select1(){return mapper1.getAll();}@GetMapping("/select2")public Object select2(){return mapper2.getAll();}
}

TreadLocal原理

官方描述: ThreadLocal类用来提供线程内部的局部变量,这种变量在多线程环境下访问(通过get和set方法访问)时,能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常是private static类型的,用于关联线程和线程的上下文。

作用: 提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少一个线程内多个函数或组件之间一些公共变量传递的复杂度。

总结:
线程并发:在多线程并发的场景下使用
传递数据:我们可以通过ThreadLocal在同一线程,不同组件中传递公共变量
线程隔离:每个线程的变量都是独立的,不会互相影响

synchronizedThreadLocal
原理同步机制采用以时间换空间的方式,只提供了一份变量,让不同的线程排队访问ThreadLocal采用以空间换时间的方式,为每一个线程都提供了一份变量的副本,从而实现同时访问而相互不干扰
侧重点多个线程之间访问资源的同步多线程中让每个线程之间的数据相互隔离

ThreadLocal设计对比JDK8前后
在这里插入图片描述
在这里插入图片描述

// JDK8的ThreadLocal下的Entry类通过继承WeakReference采用弱引用。
static class Entry extends WeakReference<ThreadLocal<?>> {
}

Memory overflow: 内存溢出,没有足够的内存提供申请者使用
Memory leak: 内存泄漏,是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度缓慢甚至系统前遗等严重后果。内存泄漏的堆积终将导致内存溢出
java中的引用有4种类型:强、软、弱、虚。当前这个问题主要涉及到强引用和弱引用
StrongReference: 强引用,就是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能证明对象还"活若”,垃圾回收器就不会回收这种对象,
WeakReference: 弱引用,垃圾回收器一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

致谢:B站UP主【我觉得吧__】 视频地址:852196951

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_11779.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

机器学习中常见性能度量汇总

前言 如果你对这篇文章可感兴趣&#xff0c;可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」&#xff0c;查看完整博客分类与对应链接。 回归 在回归任务上&#xff0c;目前最常用的性能度量方式是均方误差 (Mean Squared Error, MSE)&#xff1a; MSE1m∑i1…

flex布局

flex布局 开启flex功能 <html><head><style>.container{border: 1px solid #000;width: 400px;height: 400px;margin-left: 20px;margin-top: 20px;display: flex; // 开启flex布局}.item{width: 100px;height: 100px;color: #fff;text-align: center;line…

kubernetes(2)k8s环境搭建:kubeadm安装、二进制方式安装

环境平台规划 k8s里面之前我们了解过&#xff0c;主要的内容是master和node&#xff0c;所有这里可以划分为 单master集群多master集群 单master集群 缺点&#xff1a;master挂掉后&#xff0c;便不能去管理node节点了 多master集群 与之前的区别便是多了master 服务器硬件…

Prometheus监控进程

Prometheus监控进程 process-export主要用来做进程监控&#xff0c;比如某个服务的进程数、消耗了多少CPU、内存等资源。 一、process-exporter使用 ‍ 1.1 下载 process-exporter process-exporter GibHUB地址 process-exporter 下载地址 process-exporter可以使用命令行…

图解LeetCode——854. 相似度为 K 的字符串(难度:困难)

一、题目 对于某些非负整数 k &#xff0c;如果交换 s1 中两个字母的位置恰好 k 次&#xff0c;能够使结果字符串等于 s2 &#xff0c;则认为字符串 s1 和 s2 的 相似度为 k 。 给你两个字母异位词 s1 和 s2 &#xff0c;返回 s1 和 s2 的相似度 k 的最小值。 二、示例 2.1…

C语言手写HTTPD网站服务器

网站服务器&#xff08;HTTPD&#xff09;已经有很多版本&#xff0c;但是大部分对初学者都非常不友好。适合初学者学习的httpd服务器&#xff0c;最负盛名的当数tinyhttpd, 但是这个版本&#xff0c;是基于Linux系统的&#xff0c;而且配套的CGI也是使用perl语言写的&#xff…

宝塔面板修改secure_file_priv设置

1、secure_file_priv文件作用 mysql读取系统文件权限的设置参数 2、查询secure_file_priv设置 show variables like %secure%; 3、修改secure_file_priv设置 设置 secure_file_priv"/" 需要修改mysql配置文件my.cnf my.cnf文件有两个位置 /etc/my.cnf /www/serv…

线程安全简述

目录 1、线程是否安全 2、出现线程安全的原因如下&#xff1a; 3、原子性问题 4、synchronized关键字 1、锁对象 2、用法&#xff1a; 3、可重入锁 5、内存可见性 6、volatile关键字 7、JMM 1、线程是否安全 线程不安全就是一些代码在多线程的运行状态下&#xff0c…

一个基于.Net Core开发的适合外贸商城系统

今天给大家推荐一个适合外贸的商城系统。 项目简介 这是一个基于.Net Core开发的&#xff0c;兼容PC、平板、移动端的商城系统。被下载次数超过300w&#xff0c;拥有最活跃的成员&#xff0c;由专业团队开发与支持。支持PayPal、信用卡、发票支付。 技术架构 1、跨平台&…

Jmeter电商系统压测实战<二>

目录一、Jmeter优化tips二、Jmeter的使用建议-参数配置1. XX:MaxMataspaceSize&#xff08;jdk8的参数&#xff09;2. -Xmx2048m3. -Xms1g三、Jmeter插件1. 介绍及安装2. 常用插件四、Jmeter日志收集1. 概览2. elk&#xff0c;kibana和es的安装和配置3. Prometheus和Node Expor…

全系标配L2占比首次突破30%,「数据」赛道争夺战一触即发

智能驾驶的进阶战&#xff0c;无论是提升车型产品竞争力&#xff0c;还是为高阶功能和现有功能优化提供闭环数据迭代&#xff0c;全系标配已经成为主流趋势。 如果说智能化1.0阶段&#xff0c;车企拼的是技术的快速落地和高阶能力的标杆效应&#xff0c;那么2.0阶段就是拼规模…

python中validators库用法详解

首先安装validators库&#xff1a; pip install validators validators.between(value, minNone, maxNone) 验证一个数字value是否在最小值min和最大值max之间&#xff0c;value不仅仅可以是整数&#xff0c;也可以是其它数据类型&#xff0c;例如floats, decimals 和 dates。…

Three使用OimoPhysics实现物体相关物理特性实例

基础环境搭建&#xff1a; InstancedMesh()创建的立方体物品集合&#xff1a; boxes new THREE.InstancedMesh(new THREE.BoxGeometry(0.1, 0.1, 0.1),new THREE.MeshLambertMaterial(),100)const matrix new THREE.Matrix4()const color new THREE.Color()for (let i 0; i…

Win11 22H2 22621.521大版本更新!

注意&#xff01;注意&#xff01;Win11 22H2 22621.521大版本更新啦&#xff0c;此次更新带来了不小的优化和改进&#xff0c;包括带有标签的更新文件资源管理器、更丰富的开始菜单和任务栏体验、增强的搜索功能、对改进的安全性和无密码登录的支持等等。 让每个人都能更轻松、…

生成网络论文阅读styleGAN1(一):论文速览

研究什么内容 研究如何把生成图片当中的内容拆分开 研究方法 为了把各种风格分开先得把控制信息分开输入&#xff0c;于是作者就分开输入了&#xff0c;在PGGAN的基础上分开输入&#xff0c;取得了好的效果。 个人理解 1.这里能取得好效果的主要原因是PGGAN的逐渐提升像素…

多模块间通信存在完美的设计么?

一、前言 在 App 的使用中&#xff0c;常常会有一些功能的依赖&#xff0c;比如评论需要用户登录、支付需要用户实名绑定银行卡等。从代码开发角度而言&#xff0c;如果我们的项目使用了多模块&#xff0c;那么也就会出现模块依赖的场景&#xff0c;比如评论模块依赖登录模块提…

企业复杂的数据治理需求,TempoDF让数据开发更简单!

伴随着企业的发展以及信息化建设的不断深入&#xff0c;业务之间不关联、数据之间彼此独立、流程之间相互封闭的现象越来越普遍&#xff0c;“数据孤岛”问题愈发严重&#xff0c;已成为制约企业发展的桎梏。 为了实现企业全局数据的系统化运作管理&#xff0c;不少企业开始着…

PDF转换成PPT后格式混乱,可能这个没做好

PDF转换成PPT后格式混乱怎么处理?这类问题其实对于经常使用PPT的朋友们来说并不陌生。我们有时候需要把一篇PPT演讲稿转换成PDF文档&#xff0c;但在操作过程中常常不仅过程复杂且效果不理想。有时甚至在转化之后出现格式混乱&#xff0c;影响了阅读体验不说&#xff0c;还会让…

WPF 图片头像自由剪切器实时截图细节放大器

本文参考博文&#xff1a;WPF 自定义图片剪切器 - 头像剪切&#xff08;扩展与完善、实时截图&#xff09; 在网上找了好久都找不到合适的截图框架&#xff0c;只能用WPF 自定义图片剪切器 - 头像剪切&#xff08;扩展与完善、实时截图&#xff09;_孤夜一点星的博客-CSDN博客…

《uni-app》表单组件-form表单

本文分享的Form组件为uni-app的内置组件Form&#xff0c;非扩展组件&#xff0c;两者在用法上其实大同小异&#xff0c;只是扩展组件的属性以及事件更多…没有本质上的区别&#xff5e; 《uni-app》表单组件-form表单一. 简介二. 基础用法三. submit事件四. reset事件五. repor…