SpringBoot教程(二十四) | SpringBoot实现分布式定时任务之Quartz(多数据源配置)

SpringBoot教程(二十四) | SpringBoot实现分布式定时任务之Quartz(多数据源配置)

  • 前言
  • 多数据源配置
    • 引入aop依赖
    • 1. properties配置多数据源
    • 2. 创建数据源枚举类
    • 3. 线程参数配置类
    • 4. 数据源动态切换类
    • 5. 多数据源配置类
      • HikariCP 版本
      • Druid 版本
    • 6. 自定义多数据源切换注解
    • 7. 数据源注解截面AOP
    • 8. 测试 多数据源 是否起效

前言

我这边的SpringBoot的版本为2.6.13,其中Quartz是使用的以下方式引入

<!--quartz定时任务-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

由于我目前需要使用@QuartzDataSource去指定数据源,所以就不通过@DS注解去实现多数据源
而是通过自定义DynamicDataSource 去实现了多数据源

多数据源配置

引入aop依赖

<!--spring切面aop依赖-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

1. properties配置多数据源

# 应用服务 WEB 访问端口
server.port=9081# 主数据源
spring.datasource.db1.url=jdbc:mysql://localhost:3306/sharding?useSSL=false&serverTimezone=UTC
spring.datasource.db1.jdbc-url=jdbc:mysql://localhost:3306/sharding?useSSL=false&serverTimezone=UTC
spring.datasource.db1.username=root
spring.datasource.db1.password=root
spring.datasource.db1.driver-class-name=com.mysql.cj.jdbc.Driver# 次数据源
spring.datasource.db2.url=jdbc:mysql://localhost:3306/sharding?useSSL=false&serverTimezone=UTC
spring.datasource.db2.jdbc-url=jdbc:mysql://localhost:3306/sharding?useSSL=false&serverTimezone=UTC
spring.datasource.db2.username=root
spring.datasource.db2.password=root
spring.datasource.db2.driver-class-name=com.mysql.cj.jdbc.Driver# quartz 数据源
#spring.datasource.task.url=jdbc:mysql://localhost:3306/quartz
spring.datasource.task.jdbc-url=jdbc:mysql://localhost:3306/quartz
spring.datasource.task.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.task.username=root
spring.datasource.task.password=root# Quartz Scheduler 配置
# 指定作业存储类型为JDBC,使用数据库来存储作业和调度信息
spring.quartz.job-store-type=jdbc
# 在关闭时等待作业完成
spring.quartz.wait-for-jobs-to-complete-on-shutdown=true
# 不初始化数据库架构,假设数据库架构已经存在
spring.quartz.jdbc.initialize-schema=never
# Quartz Scheduler 属性配置
spring.quartz.properties.org.quartz.jobStore.dataSource=quartz_jobs
spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
spring.quartz.properties.org.quartz.jobStore.tablePrefix=QRTZ_
# 启用集群模式
spring.quartz.properties.org.quartz.jobStore.isClustered=true
# 集群检查间隔时间(毫秒)
spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=1000
# 不使用属性来存储作业数据,改为使用BLOB字段
spring.quartz.properties.org.quartz.jobStore.useProperties=false
# 指定作业存储的类,这里使用Spring提供的LocalDataSourceJobStore,但通常这个配置是隐式的,
# 除非你有特殊的实现需求,否则通常不需要显式设置这个属性。
# spring.quartz.properties.org.quartz.jobStore.class=org.springframework.scheduling.quartz.LocalDataSourceJobStore
# 注意:上面的行已被注释掉,因为通常不需要显式设置
# 调度器实例名称和ID(org.quartz.scheduler.instanceName 这个是保证属于同一个集群)
spring.quartz.properties.org.quartz.scheduler.instanceName=SC_Scheduler
spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO
# 线程池配置
# 线程池中线程的数量
spring.quartz.properties.org.quartz.threadPool.threadCount=25
# 线程优先级
spring.quartz.properties.org.quartz.threadPool.threadPriority=5
# 线程池实现类
spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool

2. 创建数据源枚举类

package com.example.springbootfull.quartztest.enums;/*** 数据源*/
public enum DataSourceType {/*** 数据源1* */DB1,/*** 数据源2* */DB2
}

3. 线程参数配置类

定义一个工具类来设置当前线程的数据源枚举值

package com.example.springbootfull.quartztest.datasource;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** 数据源切换处理*/
public class DynamicDataSourceContextHolder {public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);/*** 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。*/private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();/*** 设置数据源的变量*/public static void setDataSourceType(String dsType) {log.info("切换到{}数据源", dsType);CONTEXT_HOLDER.set(dsType);}/*** 获得数据源的变量*/public static String getDataSourceType() {return CONTEXT_HOLDER.get();}/*** 清空数据源变量*/public static void clearDataSourceType() {CONTEXT_HOLDER.remove();}
}

4. 数据源动态切换类

package com.example.springbootfull.quartztest.datasource;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/*** Spring的AbstractRoutingDataSource抽象类,实现动态数据源(他的作用就是动态切换数据源)* AbstractRoutingDataSource中的抽象方法determineCurrentLookupKey是实现数据源的route的核心,* 这里对该方法进行Override。【上下文DynamicDataSourceContextHolder为一线程安全的ThreadLocal】*/
public class DynamicDataSource extends AbstractRoutingDataSource {/*** 取得当前使用哪个数据源* @return dbTypeEnum*/@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getDataSourceType();}
}

5. 多数据源配置类

HikariCP 版本

package com.example.springbootfull.quartztest.config;import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.example.springbootfull.quartztest.datasource.DynamicDataSource;
import com.example.springbootfull.quartztest.enums.DataSourceType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.quartz.QuartzDataSource;
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 org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;/*** 多数据源配置*/
@Configuration
public class DataSourceConfig {/*** 创建第一个数据源** @return dataSource*/@Bean(name = "dataSource1")@ConfigurationProperties(prefix = "spring.datasource.db1")public DataSource dataSource1() {return DataSourceBuilder.create().build();}/*** 创建第二个数据源** @return dataSource*/@Bean(name = "dataSource2")@ConfigurationProperties(prefix = "spring.datasource.db2")public DataSource dataSource2() {return DataSourceBuilder.create().build();}//quartz数据库 dataSourceTask数据源//使用@QuartzDataSource后,不需要动态配置@Bean(name = "dataSourceTask")@ConfigurationProperties(prefix = "spring.datasource.task")@QuartzDataSourcepublic DataSource dataSourceTask() {return DataSourceBuilder.create().build();}/*** 动态数据源配置** @return dataSource*/@Primary@Bean("multipleDataSource")public DataSource multipleDataSource(@Qualifier("dataSource1") DataSource db1,@Qualifier("dataSource2") DataSource db2) {DynamicDataSource dynamicDataSource = new DynamicDataSource();Map<Object, Object> dataSources = new HashMap<>();dataSources.put(DataSourceType.DB1, db1);dataSources.put(DataSourceType.DB2, db2);dynamicDataSource.setTargetDataSources(dataSources);//默认数据源dynamicDataSource.setDefaultTargetDataSource(db1);return dynamicDataSource;}@Bean("sqlSessionFactory")public SqlSessionFactory sqlSessionFactory(@Qualifier("multipleDataSource") DataSource multipleDataSource) throws Exception {// 导入mybatissqlsession配置MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();// 指明数据源sessionFactory.setDataSource(multipleDataSource);// 设置mapper.xml的位置路径Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/**/*.xml");sessionFactory.setMapperLocations(resources);//指明实体扫描(多个package用逗号或者分号分隔)//sessionFactory.setTypeAliasesPackage("com.szylt.projects.project.entity");// 导入mybatis配置MybatisConfiguration configuration = new MybatisConfiguration();configuration.setJdbcTypeForNull(JdbcType.NULL);configuration.setMapUnderscoreToCamelCase(true);configuration.setCacheEnabled(false);sessionFactory.setConfiguration(configuration);return sessionFactory.getObject();}//数据源事务配置@Beanpublic PlatformTransactionManager transactionManager(DataSource multipleDataSource) {return new DataSourceTransactionManager(multipleDataSource);}}

Druid 版本

需要先引入 Druid 依赖,这里使用的是 Druid 官方的 Starter

<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.8</version>
</dependency>

然后,在properties配置文件中 为每个数据源配置DruidDataSour数据库连接池

spring.datasource.db1.type=com.alibaba.druid.pool.DruidDataSour
spring.datasource.db2.type=com.alibaba.druid.pool.DruidDataSour
spring.datasource.task.type=com.alibaba.druid.pool.DruidDataSour

接着把一下DataSourceConfig 类里面的
DataSource 对象 换成 DruidDataSource 对象
DataSourceBuilder 对象 换成 DruidDataSourceBuilder 对象
就好了

6. 自定义多数据源切换注解

package com.example.springbootfull.quartztest.annotation;import com.example.springbootfull.quartztest.enums.DataSourceType;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 自定义多数据源切换注解* 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource
{/*** 切换数据源名称*/public DataSourceType value() default DataSourceType.DB1;
}

7. 数据源注解截面AOP

package com.example.springbootfull.quartztest.aspectj;import java.util.Objects;import com.example.springbootfull.quartztest.annotation.DataSource;
import com.example.springbootfull.quartztest.datasource.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.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;/*** 多数据源处理**/
@Aspect
@Order(1)
@Component
public class DataSourceAspect {protected Logger logger = LoggerFactory.getLogger(getClass());@Pointcut("@annotation(com.example.springbootfull.quartztest.annotation.DataSource)"+ "|| @within(com.example.springbootfull.quartztest.annotation.DataSource)")public void dsPointCut() {}@Around("dsPointCut()")public Object around(ProceedingJoinPoint point) throws Throwable {DataSource dataSource = getDataSource(point);if (dataSource != null) {DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());}try {return point.proceed();} finally {// 销毁数据源 在执行方法之后DynamicDataSourceContextHolder.clearDataSourceType();}}/*** 获取需要切换的数据源*/public DataSource getDataSource(ProceedingJoinPoint point) {MethodSignature signature = (MethodSignature) point.getSignature();DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);if (Objects.nonNull(dataSource)) {return dataSource;}return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);}
}

8. 测试 多数据源 是否起效

创建了一个控制层测试类

package com.example.springbootfull.mybatisplustest.controller;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.springbootfull.mybatisplustest.entity.SysUser;
import com.example.springbootfull.mybatisplustest.mapper.SysUserMapper;
import com.example.springbootfull.quartztest.annotation.DataSource;
import com.example.springbootfull.quartztest.enums.DataSourceType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.stereotype.Controller;/*** <p>*  前端控制器* </p>** @author jiang* @since 2024-09-13*/
@Controller
@RequestMapping("/mybatisplustest/sysUser")
public class SysUserController {@Autowiredprivate SysUserMapper sysUserMapper;@RequestMapping("/selectAll")@DataSource(value = DataSourceType.DB1)public void contextLoads() {//第一页,10个Page<SysUser> page = new Page<>(1, 10);QueryWrapper<SysUser> wrapper = new QueryWrapper<>();//查询liek人1的//wrapper.eq("enabled", true).like("account", "小明机器人10号");//查询授权=true的;wrapper.like("account", "人1");IPage<SysUser> iPage = sysUserMapper.selectPage(page, wrapper);//总页数System.out.println("总页数:"+iPage.getPages());//总条数System.out.println("总条数:"+iPage.getTotal());//每页条数System.out.println("每页条数:"+iPage.getSize());//当前页的结果集System.out.println("当前页的结果集:"+iPage.getRecords());//当前页号System.out.println("当前页号:"+iPage.getCurrent());//        // 创建实体类
//        SysUser sysUser = new SysUser();
//        sysUser.setAccount("mybtis呀");
//        sysUser.setEnabled(Boolean.TRUE);
//        sysUser.setCreateAt(new Date());
//        sysUserDao.insertSelective(sysUser);
//
//        // 根据自增ID检索实体
//        SysUser sysUser1 = sysUserDao.selectByPrimaryKey(sysUser.getId());//        logger.info("user={}", sysUser1);}}

运行如下截图:
两者都有触发
在这里插入图片描述
参考文章如下:
【1】Springboot+Mybatis+MySql整合多数据源及其使用
【2】Springboot定时任务quartz整合(多数据源+quartz持久化到数据库)
【3】springboot+MybatisPlus+HikariCP多数据源动态配置(实战篇)
【4】springboot+mybatis-plus+quartz多数据源操作,亲测可用

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Java基础(2) 之面向对象

文章目录 Java基础(2) 之面向对象1.对象2.类类的注意事项 3.this关键字4.构造器注意 5.封装性6.实体JavaBean实体类 7.成员变量和局部变量的区别8.staticstatic修饰成员变量static修饰成员方法static的注意事项工具类单例设计模式 9.代码块静态代码块实例代码块 10.继承权限修饰…

Springboot——使用poi实现excel动态图片导入解析

文章目录 前言依赖引入导入实现方式一方式二 前言 最近要实现一个导入导出的功能点&#xff0c;需要能将带图片的列表数据导出到excel中&#xff0c;且可以导入带图片的excel列表数据。 考虑到低代码平台的表头与数据的不确定性&#xff0c;技术框架上暂定使用Apache-POI。 …

java 自定义填充excel并导出

首先在resources下面放一个excel模板 1. 方法签名和请求映射 RequestMapping(value "/ExportXls") public ResponseEntity<byte[]> rwzcExportXls(HttpServletRequest request, RequestBody JSONArray jsonArray) throws IOException { RequestMapping(val…

ubuntu 开放 8080 端口快捷命令

文章目录 查看防火墙状态开放 80 端口开放 8080 端口开放 22端口开启防火墙重启防火墙**使用 xhell登录**&#xff1a; 查看防火墙状态 sudo ufw status [sudo] password for crf: Status: inactivesudo ufw enable Firewall is active and enabled on system startup sudo…

微服务实战——登录(普通登录、社交登录、SSO单点登录)

登录 1.1. 用户密码 PostMapping("/login")public String login(UserLoginVo vo, RedirectAttributes redirectAttributes, HttpSession session){R r memberFeignService.login(vo);if(r.getCode() 0){MemberRespVo data r.getData("data", new Type…

进阶功法:SQL 优化指南

目录标题 SQL 优化指南1. 插入数据优化1.1 批量插入数据1.2 手动提交事务1.3 主键顺序插入1.4 大批量插入数据步骤&#xff1a; 2. 主键优化主键设计原则拓展知识 3. ORDER BY 优化3.1 Using filesort3.2 Using index示例 3.3 ORDER BY 优化原则 4. GROUP BY 优化示例 4.1 GROU…

优雅的实现服务调用 -- OpenFeign

文章目录 1. RestTemplate存在问题2. OpenFeign介绍3. 快速上手引入依赖添加注解编写OpenFeign的客户端远程调用 4. OpenFeign参数传递从URL中获取参数传递单个参数传递多个参数传递对象传递JSON 5. 最佳实践Feign继承方式创建一个新的模块引入依赖编写接口打jar包服务实现方实…

javacpp调用pdfium的c++动态库

1、.h头文件 2、生成java代码的conf PdfiumDocumentConfigure.java package org.swdc.pdfium.conf;import org.bytedeco.javacpp.annotation.Platform; import org.bytedeco.javacpp.annotation.Properties; import org.bytedeco.javacpp.tools.InfoMap; import org.byte…

物联网:一种有能力重塑世界的技术

物联网&#xff08;IoT&#xff09;近年来对我们的日常生活产生了如此积极的影响&#xff0c;以至于即使是不懂技术的人也开始相信它所带来的便利以及敏锐的洞察力。 物联网是一场数字技术革命&#xff0c;其意义甚至比工业革命更为重大。物联网是仍处于起步阶段的第四次工业革…

SldWorks问题 2. 矩阵相关接口使用上的失误

问题 在计算三维点在图纸&#xff08;DrawingDoc&#xff09;中的位置时&#xff0c;就是算不对&#xff0c;明明就4、5行代码&#xff0c;怎么看都是很“哇塞”的&#xff0c;毫无问题的。 但结果就是不对。 那就调试一下吧&#xff0c;调试后发现生成的矩阵很不对劲&#…

电力设备图像分割系统源码&数据集分享

电力设备图像分割系统系统源码&#xff06;数据集分享 [yolov8-seg-efficientViT&#xff06;yolov8-seg-C2f-DCNV2等50全套改进创新点发刊_一键训练教程_Web前端展示] 1.研究背景与意义 项目参考ILSVRC ImageNet Large Scale Visual Recognition Challenge 项目来源AAAI G…

分治算法(7)_归并排序_计算右侧小于当前元素的个数

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 分治算法(7)_归并排序_计算右侧小于当前元素的个数 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&…

鸿蒙微内核IPC数据结构

鸿蒙内核IPC数据结构 内核为任务之间的通信提供了多种机制&#xff0c;包含队列、事件、互斥锁、信号量等&#xff0c;其中还有Futex(用户态快速锁)&#xff0c;rwLock(读写锁)&#xff0c;signal(信号)。 队列 队列又称为消息队列&#xff0c;是一种常用于任务间通信的数据…

ASP.NET MVC-懒加载-逐步加载数据库信息

环境&#xff1a; win10, .NET 6.0 目录 问题描述解决方案基础版数据库查询部分&#xff08;Entity Framework&#xff09;控制器前端页面 加载到表格版 问题描述 假设我数据库中有N个表&#xff0c;当我打开某页面时&#xff0c;每个表都先加载一部分&#xff08;比如20条&am…

Chainlit集成Dashscope实现语音交互网页对话AI应用

前言 本篇文章讲解和实战&#xff0c;如何使用Chainlit集成Dashscope实现语音交互网页对话AI应用。实现方案是对接阿里云提供的语音识别SenseVoice大模型接口和语音合成CosyVoice大模型接口使用。针对SenseVoice大模型和CosyVoice大模型&#xff0c;阿里巴巴在github提供的有开…

有关vue路由的学习

导言 由于很久没碰前端了&#xff0c;碰到路由都不太会了。趁着后端对接来记录一下&#xff0c;就当复习。不过由于个人能力有限&#xff0c;这篇会偏向整个过程的实现逻辑&#xff0c;其中有很多具体的方法不会给来&#xff0c;有兴趣的可以去看一下源码~ 目的&#xff1a; …

基于springboot vue 校园失物招领平台的设计与实现

博主介绍&#xff1a;专注于Java&#xff08;springboot ssm springcloud等开发框架&#xff09; vue .net php phython node.js uniapp小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作☆☆☆ 精彩专栏推荐订阅☆☆☆☆…

SAP_SD模块-销售订单抬头折扣金额分摊到行项目的业务记录

前言&#xff1a; 本文主要是记录24年9月份支持财务月结过程中&#xff0c;用户提出的一个问题&#xff1a;“为什么KE30有部分物料9月份的销售数量少于FAGLL03H的销售数量&#xff1f;&#xff1f;”&#xff0c;主要包括以下两个内容&#xff1b; 1、问题发生的场景复现&am…

毕设分享 基于协同过滤的电影推荐系统

文章目录 0 简介1 设计概要2 课题背景和目的3 协同过滤算法原理3.1 基于用户的协同过滤推荐算法实现原理3.1.1 步骤13.1.2 步骤23.1.3 步骤33.1.4 步骤4 4 系统实现4.1 开发环境4.2 系统功能描述4.3 系统数据流程4.3.1 用户端数据流程4.3.2 管理员端数据流程 4.4 系统功能设计 …

【hot100-java】二叉树的最近公共祖先

二叉树篇 我觉得是比两个节点的深度&#xff0c;取min&#xff08;一种情况&#xff09; DFS解题。 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode(int x) { val x; }* }*/ clas…