pring-boot-2.0.3之quartz集成,数据源问题,源码探究
yuyutoo 2024-10-12 00:48 2 浏览 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大型互联网技术的视频免费分享给大家。
相关推荐
- jQuery VS AngularJS 你更钟爱哪个?
-
在这一次的Web开发教程中,我会尽力解答有关于jQuery和AngularJS的两个非常常见的问题,即jQuery和AngularJS之间的区别是什么?也就是说jQueryVSAngularJS?...
- Jquery实时校验,指定长度的「负小数」,小数位未满末尾补0
-
在可以输入【负小数】的输入框获取到焦点时,移除千位分隔符,在输入数据时,实时校验输入内容是否正确,失去焦点后,添加千位分隔符格式化数字。同时小数位未满时末尾补0。HTML代码...
- 如何在pbootCMS前台调用自定义表单?pbootCMS自定义调用代码示例
-
要在pbootCMS前台调用自定义表单,您需要在后台创建表单并为其添加字段,然后在前台模板文件中添加相关代码,如提交按钮和表单验证代码。您还可以自定义表单数据的存储位置、添加文件上传字段、日期选择器、...
- 编程技巧:Jquery实时验证,指定长度的「负小数」
-
为了保障【负小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【负小数】的方法。HTML代码<inputtype="text"class="forc...
- 一篇文章带你用jquery mobile设计颜色拾取器
-
【一、项目背景】现实生活中,我们经常会遇到配色的问题,这个时候去百度一下RGB表。而RGB表只提供相对于的颜色的RGB值而没有可以验证的模块。我们可以通过jquerymobile去设计颜色的拾取器...
- 编程技巧:Jquery实时验证,指定长度的「正小数」
-
为了保障【正小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【正小数】的方法。HTML做成方法<inputtype="text"class="fo...
- jquery.validate检查数组全部验证
-
问题:html中有多个name[],每个参数都要进行验证是否为空,这个时候直接用required:true话,不能全部验证,只要这个数组中有一个有值就可以通过的。解决方法使用addmethod...
- Vue进阶(幺叁肆):npm查看包版本信息
-
第一种方式npmviewjqueryversions这种方式可以查看npm服务器上所有的...
- layui中使用lay-verify进行条件校验
-
一、layui的校验很简单,主要有以下步骤:1.在form表单内加上class="layui-form"2.在提交按钮上加上lay-submit3.在想要校验的标签,加上lay-...
- jQuery是什么?如何使用? jquery是什么功能组件
-
jQuery于2006年1月由JohnResig在BarCampNYC首次发布。它目前由TimmyWilson领导,并由一组开发人员维护。jQuery是一个JavaScript库,它简化了客户...
- django框架的表单form的理解和用法-9
-
表单呈现...
- jquery对上传文件的检测判断 jquery实现文件上传
-
总体思路:在前端使用jquery对上传文件做部分初步的判断,验证通过的文件利用ajaxFileUpload上传到服务器端,并将文件的存储路径保存到数据库。<asp:FileUploadI...
- Nodejs之MEAN栈开发(四)-- form验证及图片上传
-
这一节增加推荐图书的提交和删除功能,来学习node的form提交以及node的图片上传功能。开始之前需要源码同学可以先在git上fork:https://github.com/stoneniqiu/R...
- 大数据开发基础之JAVA jquery 大数据java实战
-
上一篇我们讲解了JAVAscript的基础知识、特点及基本语法以及组成及基本用途,本期就给大家带来了JAVAweb的第二个知识点jquery,大数据开发基础之JAVAjquery,这是本篇文章的主要...
- 推荐四个开源的jQuery可视化表单设计器
-
jquery开源在线表单拖拉设计器formBuilder(推荐)jQueryformBuilder是一个开源的WEB在线html表单设计器,开发人员可以通过拖拉实现一个可视化的表单。支持表单常用控件...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)