Spring Boot 动态数据源

目录

前言

前置环境

pom

yml

Entity

Dao

枚举类

数据源

AOP

Controller

启动类

演示


前言

大多数系统中,都需要数据库来持久化数据,在大多数情况下,一个系统只需要配置一个数据源便能够完成所有业务的查询,保存操作。也存在一个系统需要多个数据源的情况,不同的数据源对应不同的业务操作,这种场景下配置多个数据源,并且在代码中维护多套dao层就可以了。

还存在一种业务场景,所有的业务操作都是一样的,只有操作的数据源的不同,如果用多套dao层来实现,由于业务操作都一样,会出现多块一模一样的代码,这样的冗余代码是我们不希望看到,不利于维护。这种业务场景就很适合用动态数据源来实现。

可以使用 AbstractRoutingDataSource + ThreadLocal + AOP 来实现动态数据源切换

前置环境

JDK8 + SringBoot2 + MySQL8

分别创建数据库 test1 test2

分别在两个数据库中创建 user 表

create table user (
    id int auto_increment primary key,
    username varchar(255),
    password varchar(255)
);

在test1.user 表中插入数据

insert into user(username, password) values('张三', '123456');

在test2.user 表中插入数据

insert into user(username, password) values('李四', '123456');

pom

    <dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><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></dependency><dependencies>

yml

server:port: 8888spring:datasource:primary:jdbc-url: jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=trueusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driversecondary:jdbc-url: jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=trueusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driverjpa:show-sql: trueproperties:hibernate:hbm2ddl:auto: updatedialect: org.hibernate.dialect.MySQL5InnoDBDialect

Entity

UserEntity

@Entity
@Table ( name = "user")
public class UserEntity {private Integer id;private String username;private String password;@Id@Column ( name = "id" )public int getId() {return id;}public void setId(Integer id) {this.id = id;}@Basic@Column ( name = "username" )public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}@Basic@Column ( name = "password" )public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}}

Dao

 IUserDao

@Repository (value = IUserDao.DAO_BEAN_NAME )
public interface IUserDao extends JpaRepository<UserEntity, Long> {String DAO_BEAN_NAME = "userDao";
}

枚举类

枚举动态数据源,提高代码可读性

DataSourceEnums

public enum DataSourceEnums {PRIMARY,SECONDARY;static {set = Arrays.stream(values()).map(e -> e.name()).collect(Collectors.toSet());}private static Set<String> set;public static boolean isValid(String dataSource) {return set.contains(dataSource);}
}

数据源

线程切换数据源上下文,每个请求线程都维护一个自己的当前数据源变量

DynamicDataSourceContenxtHolder

public class DynamicDataSourceContextHolder {/*** 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,*  所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。*/private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();/*** 设置数据源变量* @param dataSourceType*/public static void setDataSourceType(String dataSourceType){System.out.printf("切换到{%s}数据源", dataSourceType);CONTEXT_HOLDER.set(dataSourceType);}/*** 获取数据源变量* @return*/public static String getDataSourceType(){return CONTEXT_HOLDER.get();}/*** 清空数据源变量*/public static void clearDataSourceType(){CONTEXT_HOLDER.remove();}
}

DynamicDataSource

public class DynamicDataSource extends AbstractRoutingDataSource {public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {super.setDefaultTargetDataSource(defaultTargetDataSource);super.setTargetDataSources(targetDataSources);// afterPropertiesSet()方法调用时用来将targetDataSources的属性写入resolvedDataSources中的super.afterPropertiesSet();}/*** 根据Key获取数据源的信息** @return*/@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getDataSourceType();}
}

DynamicDatasourceConfig

记得将类中的两个包路径修改成自己项目的包路径

REPOSITORY_PACKAGE

ENTITY_PACKAGE

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories (basePackages = DynamicDatasourceConfig.REPOSITORY_PACKAGE,entityManagerFactoryRef = "dynamicEntityManagerFactory",transactionManagerRef = "dynamicTransactionManager"
)
public class DynamicDatasourceConfig {//--------------数据源配置-------------------@Bean(name="primary")@ConfigurationProperties(prefix = "spring.datasource.primary")public DataSource primaryDataSource() {return DataSourceBuilder.create().build();}@Bean(name="secondary")@ConfigurationProperties(prefix = "spring.datasource.secondary")public DataSource secondaryDataSource() {return DataSourceBuilder.create().build();}@Primary@Bean(name = "dynamicDataSource")public DynamicDataSource dataSource(@Qualifier ("primary") DataSource primaryDataSource, @Qualifier ( "secondary" )DataSource secondaryDataSource) {Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put(DataSourceEnums.PRIMARY.name(), primaryDataSource);targetDataSources.put(DataSourceEnums.SECONDARY.name(), secondaryDataSource);return new DynamicDataSource(primaryDataSource, targetDataSources);}/*** 该方法仅在需要使用JdbcTemplate对象时选用** @param dataSource 注入名为dynamicDataSource的bean* @return 数据源JdbcTemplate对象*/@Bean(name = "dynamicJdbcTemplate")public JdbcTemplate jdbcTemplate(@Qualifier("dynamicDataSource") DataSource dataSource) {return new JdbcTemplate(dataSource);}//-------------jpa配置---------------static final String REPOSITORY_PACKAGE = "com.your.dao";private static final String ENTITY_PACKAGE = "com.your.entity";/*** 扫描spring.jpa.dynamic开头的配置信息** @return jpa配置信息*/@Primary@Bean (name = "dynamicJpaProperties")@ConfigurationProperties (prefix = "spring.jpa")public JpaProperties jpaProperties() {return new JpaProperties();}/*** 获取主库实体管理工厂对象** @param dynamicDataSource 注入名为dynamicDataSource的数据源* @param jpaProperties     注入名为dynamicJpaProperties的jpa配置信息* @param builder           注入EntityManagerFactoryBuilder* @return 实体管理工厂对象*/@Primary@Bean(name = "dynamicEntityManagerFactory")public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(@Qualifier ("dynamicDataSource") DataSource dynamicDataSource,@Qualifier("dynamicJpaProperties") JpaProperties jpaProperties,EntityManagerFactoryBuilder builder) {return builder// 设置数据源.dataSource(dynamicDataSource)// 设置jpa配置.properties(jpaProperties.getProperties())// 设置实体包名.packages(ENTITY_PACKAGE)// 设置持久化单元名,用于@PersistenceContext注解获取EntityManager时指定数据源.persistenceUnit("dynamicPersistenceUnit").build();}/*** 获取实体管理对象** @param factory 注入名为dynamicEntityManagerFactory的bean* @return 实体管理对象*/@Primary@Bean(name = "dynamicEntityManager")public EntityManager entityManager(@Qualifier("dynamicEntityManagerFactory") EntityManagerFactory factory) {return factory.createEntityManager();}/*** 获取主库事务管理对象** @param factory 注入名为dynamicEntityManagerFactory的bean* @return 事务管理对象*/@Primary@Bean(name = "dynamicTransactionManager")public JpaTransactionManager transactionManager(@Qualifier("dynamicEntityManagerFactory") EntityManagerFactory factory) {return new JpaTransactionManager(factory);}}

AOP

对需要动态切换数据源的请求做切面,编写切换数据源逻辑

记得将切点表达式换成自己项目的路径

 @Pointcut ("execution(* com.your.controller.DynamicController.*(..))")

 DataSourceAspect

@Aspect
@Component
public class DataSourceAspect {@Pointcut ("execution(* com.your.controller.DynamicController.*(..))")public void dsPointCut() {}@Around ("dsPointCut()")public Object around(ProceedingJoinPoint point) throws Throwable {RequestAttributes ra = RequestContextHolder.getRequestAttributes();ServletRequestAttributes sra = (ServletRequestAttributes) ra;HttpServletRequest request = sra.getRequest();String dataSource = request.getParameter("database");if (dataSource != null && DataSourceEnums.isValid(dataSource)) {DynamicDataSourceContextHolder.setDataSourceType(dataSource);}try {return point.proceed();} finally {// 销毁数据源 在执行方法之后DynamicDataSourceContextHolder.clearDataSourceType();}}
}

Controller

DynamicController

@RestController
@RequestMapping(value = "/test")
public class DynamicController {@ResourceIUserDao userDao;@GetMapping(value = "/findAll")public List<UserEntity> findAll() {return userDao.findAll();}
}

启动类

由于使用的是自定义的数据源配置,在启动时需要将Spring Boot 中扫描默认数据源的配置类排除掉,注解如下所示:

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableTransactionManagement
public class DynamicApplication {public static void main(String[] args) {SpringApplication.run(DynamicApplication.class, args);}
}

演示

请求 /test/findAll 或者 /test/findAll?database=PRIMARY

不传默认是 PRIMARY 数据库

请求 /test/findAll?database=SECONDARY

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

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

相关文章

为什么Transformer需要进行 Multi-head Attention?

目录 1. 前言 2. 基本概念 2.1. Word2Vec 2.2. Attention is all you need 2.3. Self-attention 2.3.1. 概述self-attention 2.3.2. 训练细节 2.4. Multi-head Attention 2.4.1. 多头理论细节 2.4.2. 多头代码实现 2.5. 总结 3. 讨论观点 3.1. 观点1&#xff1a; …

【工具插件类教学】vHierarchy 2工具编辑器扩展使用

目录 一、下载导入 二、使用介绍 1.便捷小工具 a.图标和颜色Icons and colors b.对象组件缩略图Component minimap c.层级线展示Hierarchy lines d.极简模式Minimal mode e.斑马条纹图案Zebra striping f.激活切换Activation toggle 2、快捷键 一、下载导入 资源官方…

二维码门楼牌管理应用平台建设:流程优化与全面考量

文章目录 前言一、工作流程优化&#xff1a;移动端采集与实时更新二、数据完整性与准确性保障三、效率提升与成本节约四、扩展性与未来发展五、数据安全与隐私保护六、用户培训与技术支持 前言 随着智慧城市建设的不断深入&#xff0c;二维码门楼牌管理应用平台作为城市管理的…

数据库事务处理技术——故障恢复

1. 数据故障恢复的宏观思路 我们知道DBMS是利用内存&#xff08;主存&#xff09;和外存&#xff08;辅存&#xff09;这样的存储体系进行数据库的管理&#xff0c;其中内存也就是我们常说的缓存是易失的。而事务时DBMS对数据库进行控制的基本单元&#xff0c;宏观上是由程序设…

算法训练.

一.奶牛晒衣服 题解&#xff1a; 这应该是个二分题&#xff0c;但是我用的是贪心暴力写的&#xff0c;思想就是循坏每次都让湿度最高的使用一次烘衣机&#xff0c;要是湿度最高的可以在自然条件下都能晒干就结束循环&#xff0c;这样内部我第一想法就是每次都排个降序&#xf…

windows下在线预览服务kkFileView4.4.0问题记录

前几天找到一个开源项目&#xff1a;kkFileView&#xff0c;感觉可能以后可能会用到&#xff0c;所以尝试了下。 通过git下载下来&#xff0c;版本是4.4.0&#xff0c;通过idea打开项目&#xff0c;发现老是无法找到组件aspose-cad&#xff0c;版本是23.9. 找了好多文章&#x…

AI学习(1)软件的选择,cuda和pytorch的安装

文章目录 1.使用VScode开发&#xff0c;结合anaconda配置python环境2.安装pytorch库3.深度学习相关的库1.numpy&#xff08;科学计算库&#xff09;2.pandas(数据分析处理库)3.matplotlib&#xff08;可视化库&#xff09;4.seaborn&#xff08;可视化库&#xff09; 1.使用VSc…

Docker三大基础组件

Docker有三个重要的概念&#xff1a;仓库、镜像和容器 &#xff0c;它们是Docker的三大基础组件&#xff0c;这三个组件共同构成了Docker的核心架构&#xff0c;使得Docker能够实现对应用程序的便捷打包、分发和运行。 Docker使用客户端-服务器体系结构。Docker客户端与Docker守…

安装jdk和tomcat

安装nodejs 1.安装nodejs&#xff0c;这是一个jdk一样的软件运行环境 yum -y list installed|grep epel yum -y install nodejs node -v 2.下载对应的nodejs软件npm yum -y install npm npm -v npm set config .....淘宝镜像 3.安装vue/cli command line interface 命令行接…

【Qt】QDateTimeEdit

在Qt中&#xff0c;QDateEdit是用于选择日期的微调框&#xff0c;QTimeEdit是用于选择小时和分钟的微调框 QDateTimeEdit则是基于QDateEdit和QTimeEdit的组合控件&#xff0c;能够同时显示日期和时间&#xff0c;并允许用户以交互方式编辑日期 常用属性 属性说明dateTime时间…

SpringBoot SseEmitter,服务器单项消息推送

防止推送消息乱码 import org.jetbrains.annotations.NotNull; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.servlet.mvc.method…

每日OJ_牛客HJ74 参数解析

目录 牛客HJ74 参数解析 解析代码1 解析代码2 牛客HJ74 参数解析 参数解析_牛客题霸_牛客网 解析代码1 本题通过以空格和双引号为间隔&#xff0c;统计参数个数。对于双引号&#xff0c;通过添加flag&#xff0c;保证双引号中的空格被输出。 #include <iostream> #i…

Ubuntu20.04安装Angular CLI

一、更换apt-get源 使用原来的apt-get源有几个包报错&#xff0c;下不下来 更换阿里源&#xff08;阿里巴巴开源镜像站-OPSX镜像站-阿里云开发者社区&#xff09;&#xff0c;使用网站中的内容&#xff0c;在 apt-get update 时总是报错 改用清华源&#xff1a; deb http:/…

学习日记:二维数组

目录 1. 定义 2. 初始化 3. 数组元素的引用 4. 二维字符型数组 4.1 初始化 1. 定义 C语言中并不存在真正的二维数组&#xff1b; 二维数组的本质&#xff1a;一维数组类型的一维数组。 二维数组数据存储时按行优先存储。 语法&#xff1a; 类型说明符 数组名 [常量表达…

java单链表;双向链表;双向循环链表——简单应用

一、链表(Linked List)介绍 链表是有序的列表&#xff0c;但是它在内存中是存储如下 链表是以节点的方式来存储,是链式存储每个节点包含 data 域&#xff0c; next 域&#xff1a;指向下一个节点.如图&#xff1a;发现链表的各个节点不一定是连续存储.链表分带头节点的链表和没…

LLM实战系列(1)—强强联合Langchain-Vicuna应用实战

背景 本文主要介绍一下&#xff0c;基于Langchain与Vicuna-13B的外挂OceanBase知识库项目实战以及QA使用&#xff0c;项目地址: github.com/csunny/DB-G… 在开始之前&#xff0c;我们还是先看看效果&#xff5e; 自Meta发布LLaMA大模型以来&#xff0c; 围绕LLaMA微调的模型…

基于PHP+MySQL组合开发的微信活动投票小程序源码系统 带完整的安装代码包以及搭建部署教程

系统概述 在当今数字化时代&#xff0c;微信作为社交媒体的巨头&#xff0c;为企业和个人提供了丰富的互动营销平台。其中&#xff0c;投票活动作为一种有效的用户参与和互动方式&#xff0c;被广泛应用于各种场景。为了满足这一需求&#xff0c;我们推出了一款基于PHPMySQL组…

W1R3S靶机全通详细教程

文章目录 w1r3s主机发现主机扫描 端口扫描tcp端口扫描UDP扫描漏洞扫描 攻击面分析FTP渗透匿名登录 web渗透目录爆破 cuppa cms文件包含漏洞getshell提权 w1r3s 引言 近些日子看红笔大佬的靶机精讲视频时&#xff0c;他的一句话让我感受颇深&#xff0c;很多视频在讲解时&…

数据结构:线性表(下)

那么这篇就来总结一下栈和队列 一、栈 栈 (Stack) 只允许在有序的线性数据集合的一端&#xff08;称为栈顶 top&#xff09;进行加入数据&#xff08;push&#xff09;和移除数据&#xff08;pop&#xff09;。因而按照 后进先出&#xff08;LIFO, Last In First Out&#xf…