百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

pring-boot-2.0.3之quartz集成,数据源问题,源码探究

yuyutoo 2024-10-12 00:48 8 浏览 0 评论

数据源问题

产生背景

如果定时任务不服务于业务,那将毫无意义;我们不能让定时任务只是空跑(或者打印一句:MyJob...),如果是,那么相信我,把这个定时任务删了吧,不要有任何留恋!

既然是服务于我们的业务,那么很大程度上就会操作数据库;我的业务需求就是凌晨某个时间点进行一次数据统计,既要从数据库查数据,也要将统计后的数据插入到数据库。那么问题来了,业务job中如何操作数据库?

业务job示例

如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。

package com.lee.quartz.job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
public class MyJob extends QuartzJobBean {
 private static final Logger LOGGER = LoggerFactory.getLogger(MyJob.class);
 @Override
 protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
 // TODO 如何进行数据库的操作
 System.out.println("MyJob...")
 }
}

可以从4个方面来考虑(业务job中如何操作数据库):

1、既然是springboot与quartz的集成,那么我们能不能用spring的注入功能,将我们的mapper(集成了mybatis)注入到业务job中了?

2、利用JobDetail的jobDataMap,将我们的mapper传到业务job中

3、quartz不是有它自己的11张表吗,那它肯定有对数据库进行操作,我们参考quartz是如何操作数据库的

4、实在是不行,我们自己创建数据库连接总行了吧

我们来逐个分析下以上4种方案

方案4,个人不推荐,个人比较推荐连接池的方式来管理数据库连接,但个人实现数据库连接池已经是个不小的挑战了,没必要;不到万不得已不采用此方案。

如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。

方案1,这个听起来好像很不错,连接交由spring的数据源管理,我们只需要用其中的连接操作数据库即可。但看上面的MyJob,spring管理的bean能注入进来吗,显然不能,因为MyJob实例不受spring管理;有小伙伴可能会认为这很简单,MyJob实例让spring管理起来不就OK 了! ok,问题又来了,spring管理的MyJob实例能用到quartz中吗,不能! quartz如何获取MyJob实例? 我们把MyJob的类全路径:com.lee.quartz.job.MyJob传给了quartz,那么很显然quartz会根据这个类全路径,然后通过反射来实例化MyJob(这也是为什么业务Job一定要有无参构造方法的原因),也就是quartz会重新创建MyJob实例,与spring管理MyJob实例没有任何关系。显然通过spring注入的方式是行不通的。

方案2,我们知道可以通过JobDetail进行参数的传递,但有要求:传递的参数必须能序列化(实现Serializable);我没测试此方案,不过我想实现起来会有点麻烦。

方案3,这个好像可行,我们可以看看quartz是如何进行数据库操作的,我们把quartz的那套拿过来用是不是就行了呢?

说了这么多,方案总结下:

1、如何利用quartz的数据源(或者数据库连接)进行数据库操作

2、引申下,能不能将quart的数据源设置成我们应用的数据源,让quartz与应用共用一个数据源,方便统一管理?

源码探究

1、quartz自身是如何操作数据库的

我们通过暂停任务来跟下源代码,如下图所示

如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。

发现获取connection的方式如下所示:

conn = DBConnectionManager.getInstance().getConnection(getDataSource());

很明显,DBConnectionManager是单例的,通过DBConnectionManager从数据源中获取数据库连接(conn),既然都拿到conn了,那操作数据库也就简单了。注意:getDataSource()获取的是数据源的名称,不是数据源!

接下来我们再看看数据源是什么数据源,druid?还是quartz自己的数据源?


数据源还是用的我们应用的数据源(druid数据源),springboot自动将我们应用的数据源配置给了quartz。

至此,该问题也就清晰了,总结下:springboot会自动将我们的应用数据源(druid数据源)配置给quartz,quartz操作数据库的时候从数据源中获取数据库连接,然后通过数据库连接对数据库进行操作。

2、springboot是如何设置quartz数据源的

凡是涉及到springboot自动配置的,去找spring-boot-autoconfigure-2.0.3.RELEASE.jar中spring.factories就对了,如下所示


关于spring.factories文件内容的读取,大家查阅此篇博文;关于springboot的自动配置,我的springboot启动源码系列篇中还没有讲到。大家姑且先这样认为:

当在类路径下能找到Scheduler.class, SchedulerFactoryBean.class,PlatformTransactionManager.class时(只要pom.xml有spring-boot-starter-quartz依赖,这些类就能在类路径下找到),QuartzAutoConfiguration就会被springboot当成配置类进行自动配置。


将quartz的配置属性设置给SchedulerFactoryBean;将数据源设置给SchedulerFactoryBean:如果有@QuartzDataSource修饰的数据源,则将@QuartzDataSource修饰的数据源设置给SchedulerFactoryBean,否则将应用的数据源(druid数据源)设置给SchedulerFactoryBean,显然我们的应用中没有@QuartzDataSource修饰的数据源,那么SchedulerFactoryBean中的数据源就是应用的数据源;将事务管理器设置给SchedulerFactoryBean。

SchedulerFactoryBean,Scheduler的工程bean,负责创建和配置quartz Scheduler;它实现了FactoryBean、InitializingBean,FactoryBean的getObject方法实现的很简单,如下

@Override
@Nullable
public Scheduler getObject() {
 return this.scheduler;
}

就是返回scheduler实例,注册到spring容器中,那么scheduler是在哪里实例化的呢,就是在afterPropertiesSet中完成的,关于FactoryBean、InitializingBean本文不做过多的讲解,不了解的可以先去查阅下资料(注意:InitializingBean的afterPropertiesSet()先于FactoryBean的getObject()执行)。接下来我们仔细看看SchedulerFactoryBean实现InitializingBean的afterPropertiesSet方法

@Override
public void afterPropertiesSet() throws Exception {
 if (this.dataSource == null && this.nonTransactionalDataSource != null) {
 this.dataSource = this.nonTransactionalDataSource;
 }
 if (this.applicationContext != null && this.resourceLoader == null) {
 this.resourceLoader = this.applicationContext;
 }
 // Initialize the Scheduler instance... 初始化Scheduler实例
 this.scheduler = prepareScheduler(prepareSchedulerFactory());
 try {
 registerListeners(); // 注册Scheduler相关监听器,一般没有
 registerJobsAndTriggers(); // 注册jobs和triggers, 一般没有
 }
 catch (Exception ex) {
 try {
 this.scheduler.shutdown(true);
 }
 catch (Exception ex2) {
 logger.debug("Scheduler shutdown exception after registration failure", ex2);
 }
 throw ex;
 }
}

如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。

我们来重点跟下:this.scheduler = prepareScheduler(prepareSchedulerFactory());


可以看到我们通过org.quartz.jobStore.dataSource设置的dsName(quartzDs)最后会被替换成springTxDataSource.加scheduler实例名(我们的应用中是:springTxDataSource.quartzScheduler),这也就是为什么我们通过DBConnectionManager.getInstance().getConnection("quartzDs")报以下错误的原因

java.sql.SQLException: There is no DataSource named 'quartzDs'
 at org.quartz.utils.DBConnectionManager.getConnection(DBConnectionManager.java:104)
 at com.lee.quartz.job.FetchDataJob.executeInternal(FetchDataJob.java:24)
 at org.springframework.scheduling.quartz.QuartzJobBean.execute(QuartzJobBean.java:75)
 at org.quartz.core.JobRunShell.run(JobRunShell.java:202)
 at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573)


LocalDataSourceJobStore的initialize内容如下

@Override
 public void initialize(ClassLoadHelper loadHelper, SchedulerSignaler signaler) throws SchedulerConfigException {
 // Absolutely needs thread-bound DataSource to initialize.
 this.dataSource = SchedulerFactoryBean.getConfigTimeDataSource();
 if (this.dataSource == null) {
 throw new SchedulerConfigException("No local DataSource found for configuration - " +
 "'dataSource' property must be set on SchedulerFactoryBean");
 }
 // Configure transactional connection settings for Quartz.
 setDataSource(TX_DATA_SOURCE_PREFIX + getInstanceName());
 setDontSetAutoCommitFalse(true);
 // Register transactional ConnectionProvider for Quartz.
 DBConnectionManager.getInstance().addConnectionProvider(
 TX_DATA_SOURCE_PREFIX + getInstanceName(),
 new ConnectionProvider() {
 @Override
 public Connection getConnection() throws SQLException {
 // Return a transactional Connection, if any.
 return DataSourceUtils.doGetConnection(dataSource);
 }
 @Override
 public void shutdown() {
 // Do nothing - a Spring-managed DataSource has its own lifecycle.
 }
 /* Quartz 2.2 initialize method */
 public void initialize() {
 // Do nothing - a Spring-managed DataSource has its own lifecycle.
 }
 }
 );
 // Non-transactional DataSource is optional: fall back to default
 // DataSource if not explicitly specified.
 DataSource nonTxDataSource = SchedulerFactoryBean.getConfigTimeNonTransactionalDataSource();
 final DataSource nonTxDataSourceToUse = (nonTxDataSource != null ? nonTxDataSource : this.dataSource);
 // Configure non-transactional connection settings for Quartz.
 setNonManagedTXDataSource(NON_TX_DATA_SOURCE_PREFIX + getInstanceName());
 // Register non-transactional ConnectionProvider for Quartz.
 DBConnectionManager.getInstance().addConnectionProvider(
 NON_TX_DATA_SOURCE_PREFIX + getInstanceName(),
 new ConnectionProvider() {
 @Override
 public Connection getConnection() throws SQLException {
 // Always return a non-transactional Connection.
 return nonTxDataSourceToUse.getConnection();
 }
 @Override
 public void shutdown() {
 // Do nothing - a Spring-managed DataSource has its own lifecycle.
 }
 /* Quartz 2.2 initialize method */
 public void initialize() {
 // Do nothing - a Spring-managed DataSource has its own lifecycle.
 }
 }
 );
 // No, if HSQL is the platform, we really don't want to use locks...
 try {
 String productName = JdbcUtils.extractDatabaseMetaData(this.dataSource, "getDatabaseProductName");
 productName = JdbcUtils.commonDatabaseName(productName);
 if (productName != null && productName.toLowerCase().contains("hsql")) {
 setUseDBLocks(false);
 setLockHandler(new SimpleSemaphore());
 }
 }
 catch (MetaDataAccessException ex) {
 logWarnIfNonZero(1, "Could not detect database type. Assuming locks can be taken.");
 }
 super.initialize(loadHelper, signaler);
 }


注册两个ConnectionProvider给quartz:一个dsName叫springTxDataSource.quartzScheduler,有事务;一个dsName叫springNonTxDataSource.quartzScheduler,没事务;所以我们通过DBConnectionManager获取connection时,通过指定dsName就能获取支持事务或不支持事务的connection。

另外,SchedulerFactoryBean实现了SmartLifecycle,会在ApplicationContext refresh的时候启动Schedule,ApplicationContext shutdown的时候停止Schedule。

总结

1、springboot集成quartz,应用启动过程中会自动调用schedule的start方法来启动调度器,也就相当于启动了quartz,原因是SchedulerFactoryBean实现了SmartLifecycle接口;

2、springboot会自动将我们应用的数据源配置给quartz,在我们示例应用中数据源是druid数据源,应用和quartz都是用的此数据源;

3、通过org.quartz.jobStore.dataSource设置的数据源名会被覆盖掉,当我们通过quartz的DBConnectionManager获取connection时,默认情况dbName给springTxDataSource.quartzScheduler或者springNonTxDataSource.quartzScheduler,一个支持事务,一个不支持事务;至于怎样自定义dsName,我还没去尝试,有兴趣的小伙伴可以自己试试;

4、springboot集成quartz,只是将quartz的一些通用配置给配置好了,如果我们对quartz十分熟悉,那么就很好理解,但如果对quartz不熟悉(楼主对quartz就不熟悉),那么很多时候出了问题就无从下手了,所以建议大家先熟悉quartz;

如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。

相关推荐

ETCD 故障恢复(etc常见故障)

概述Kubernetes集群外部ETCD节点故障,导致kube-apiserver无法启动。...

在Ubuntu 16.04 LTS服务器上安装FreeRADIUS和Daloradius的方法

FreeRADIUS为AAARadiusLinux下开源解决方案,DaloRadius为图形化web管理工具。...

如何排查服务器被黑客入侵的迹象(黑客 抓取服务器数据)

---排查服务器是否被黑客入侵需要系统性地检查多个关键点,以下是一份详细的排查指南,包含具体命令、工具和应对策略:---###**一、快速初步检查**####1.**检查异常登录记录**...

使用 Fail Ban 日志分析 SSH 攻击行为

通过分析`fail2ban`日志可以识别和应对SSH暴力破解等攻击行为。以下是详细的操作流程和关键分析方法:---###**一、Fail2ban日志位置**Fail2ban的日志路径因系统配置...

《5 个实用技巧,提升你的服务器安全性,避免被黑客盯上!》

服务器的安全性至关重要,特别是在如今网络攻击频繁的情况下。如果你的服务器存在漏洞,黑客可能会利用这些漏洞进行攻击,甚至窃取数据。今天我们就来聊聊5个实用技巧,帮助你提升服务器的安全性,让你的系统更...

聊聊Spring AI Alibaba的YuQueDocumentReader

序本文主要研究一下SpringAIAlibaba的YuQueDocumentReaderYuQueDocumentReader...

Mac Docker环境,利用Canal实现MySQL同步ES

Canal的使用使用docker环境安装mysql、canal、elasticsearch,基于binlog利用canal实现mysql的数据同步到elasticsearch中,并在springboo...

RustDesk:开源远程控制工具的技术架构与全场景部署实战

一、开源远程控制领域的革新者1.1行业痛点与解决方案...

长安汽车一代CS75Plus2020款安装高德地图7.5

不用破解原车机,一代CS75Plus2020款,安装车机版高德地图7.5,有红绿灯读秒!废话不多讲,安装步骤如下:一、在拨号状态输入:在电话拨号界面,输入:*#518200#*(进入安卓设置界面,...

Zookeeper使用详解之常见操作篇(zookeeper ui)

一、Zookeeper的数据结构对于ZooKeeper而言,其存储结构类似于文件系统,也是一个树形目录服务,并通过Key-Value键值对的形式进行数据存储。其中,Key由斜线间隔的路径元素构成。对...

zk源码—4.会话的实现原理一(会话层的基本功能是什么)

大纲1.创建会话...

Zookeeper 可观测性最佳实践(zookeeper能够确保)

Zookeeper介绍ZooKeeper是一个开源的分布式协调服务,用于管理和协调分布式系统中的节点。它提供了一种高效、可靠的方式来解决分布式系统中的常见问题,如数据同步、配置管理、命名服务和集群...

服务器密码错误被锁定怎么解决(服务器密码错几次锁)

#服务器密码错误被锁定解决方案当服务器因多次密码错误导致账户被锁定时,可以按照以下步骤进行排查和解决:##一、确认锁定状态###1.检查账户锁定状态(Linux)```bash#查看账户锁定...

zk基础—4.zk实现分布式功能(分布式zk的使用)

大纲1.zk实现数据发布订阅...

《死神魂魄觉醒》卡死问题终极解决方案:从原理到实战的深度解析

在《死神魂魄觉醒》的斩魄刀交锋中,游戏卡死犹如突现的虚圈屏障,阻断玩家与尸魂界的连接。本文将从技术架构、解决方案、预防策略三个维度,深度剖析卡死问题的成因与应对之策,助力玩家突破次元壁障,畅享灵魂共鸣...

取消回复欢迎 发表评论: