quartz定时任务动态配置详细教程(附demo)(三)
来源:互联网 发布:java微信接口开发 编辑:程序博客网 时间:2024/06/16 02:54
源码地址:https://gitee.com/seek412/quartz03.git
在上一章节中介绍了spring整合quartz定时任务,但是想要修改、停止定时任务就必须重启服务器。实际需求往往更复杂,下面介绍如何在前端页面控制定时任务的启动,暂停,修改定时任务的时间等
项目结构
步骤1:数据库设计
在上一章介绍了spring整合quartz,下面是配置文件
<!-- class文件对应定时任务的路径,如果有多个定时任务就配置多个 --> <bean id="quartzJobA" class="com.demo.schedule.QuartzJobA"/> <bean id="quartzJobB" class="com.demo.schedule.QuartzJobB"/> <bean id="jobDetailA" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <!-- 这里的name必须是targetObject和targetMethod --> <property name="targetObject" ref="quartzJobA"/> <property name="targetMethod" value="jobA"/> </bean> <bean id="jobDetailB" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject" ref="quartzJobB"/> <property name="targetMethod" value="jobB"/> </bean> <!-- 定义触发的条件 --> <bean id="jobTriggerA" class="org.springframework.scheduling.quartz.CronTriggerBean"> <!-- 这里的name必须是jobDetail和cronExpression --> <property name="jobDetail" ref="jobDetailA"/> <!-- 每隔5秒执行一次 --> <property name="cronExpression" value="0/5 * * * * ?"/> </bean> <bean id="jobTriggerB" class="org.springframework.scheduling.quartz.CronTriggerBean"> <property name="jobDetail" ref="jobDetailB"/> <property name="cronExpression" value="0/5 * * * * ?"/> </bean> <!-- 总管理类,如果将lazy-init='false',那么容器启动就会执行调度程序 --> <bean id="startQuartz" lazy-init="false" autowire="no" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="jobTriggerA"/> <ref bean="jobTriggerB"/> </list> </property> </bean>
从上面的配置文件中我们可以看到,有如下字段需要我们指定:
bean id
bean class:需要执行的定时任务的路径
targetMethod:需要执行的定时任务的方法
Expression:定时任务执行频率
设计数据库字段如下:
CREATE TABLE `schedule_job` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主键', `job_name` varchar(255) DEFAULT NULL COMMENT '任务名', `job_group` varchar(255) DEFAULT NULL COMMENT '任务组', `method_name` varchar(255) DEFAULT NULL COMMENT '要执行的方法', `bean_class` varchar(255) DEFAULT NULL COMMENT '定时任务所在的类路径', `status` int(11) DEFAULT NULL COMMENT '任务状态 0:正常 1:暂停', `cron_expression` varchar(255) DEFAULT NULL COMMENT '时间表达式', `params` varchar(255) DEFAULT NULL COMMENT '参数', `remark` varchar(255) DEFAULT NULL COMMENT '备注', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `modify_time` datetime DEFAULT NULL COMMENT '修改时间', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
步骤2:搭建ssm开发环境
2.1 引入依赖包
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.demo</groupId> <artifactId>quartz03</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>quartz03 Maven Webapp</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <!-- 引入项目依赖的jar包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.3.7.RELEASE</version> </dependency> <!-- spring jdbc--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.3.7.RELEASE</version> </dependency> <!-- spring面向切面编程 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>4.3.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>4.3.7.RELEASE</version> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.2</version> </dependency> <!-- mybatis与spring整合包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.1</version> </dependency> <!-- mybatis逆向工程--> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.3.5</version> </dependency> <!-- pageHelper分页插件--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.0.0</version> </dependency> <!-- 数据库连接池druid--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.18</version> </dependency> <!-- 数据库驱动包--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.40</version> </dependency> <!--servlet相关--> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> </dependency> <!-- 引入org.apache.commons.lang包--> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency> <!-- quartz作业调度--> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.0</version> </dependency> <!-- json解析 --> <dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-mapper-asl</artifactId> <version>1.9.13</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.7.4</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.7.4</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.7.4</version> </dependency> </dependencies> <build> <finalName>quartz03</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.7</source> <target>1.7</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> </resources> </build></project>
2.2 添加配置文件
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd"> <!-- 1. 配置数据库配置文件 --> <bean id = "property" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:database.properties</value> </list> </property> </bean> <!-- 2. 使用druid连接数据库 --> <bean id = "dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method = "close"> <!-- 数据库基本信息配置 --> <property name = "url" value = "${url}" /> <property name = "username" value = "${username}" /> <property name = "password" value = "${password}" /> <property name = "driverClassName" value = "${driverClassName}" /> <property name = "filters" value = "${filters}" /> <!-- 最大并发连接数 --> <property name = "maxActive" value = "${maxActive}" /> <!-- 初始化连接数量 --> <property name = "initialSize" value = "${initialSize}" /> <!-- 配置获取连接等待超时的时间 --> <property name = "maxWait" value = "${maxWait}" /> <!-- 最小空闲连接数 --> <property name = "minIdle" value = "${minIdle}" /> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <property name = "timeBetweenEvictionRunsMillis" value ="${timeBetweenEvictionRunsMillis}" /> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --> <property name = "minEvictableIdleTimeMillis" value ="${minEvictableIdleTimeMillis}" /> <!-- 用来检测连接是否有效sql,要求是一个查询语句 --> <property name = "validationQuery" value = "${validationQuery}" /> <!-- 建议配置为true,不影响性能,并且保证安全性 --> <property name = "testWhileIdle" value = "${testWhileIdle}" /> <!-- 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 --> <property name = "testOnBorrow" value = "${testOnBorrow}" /> <!-- 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 --> <property name = "testOnReturn" value = "${testOnReturn}" /> <!-- 要启用PSCache,必须配置大于0 --> <property name = "maxOpenPreparedStatements" value ="${maxOpenPreparedStatements}" /> <!-- 打开 removeAbandoned 功能 --> <property name = "removeAbandoned" value = "${removeAbandoned}" /> <!-- 1800 秒,也就是 30 分钟 --> <property name = "removeAbandonedTimeout" value ="${removeAbandonedTimeout}" /> <!-- 关闭 abanded 连接时输出错误日志 --> <property name = "logAbandoned" value = "${logAbandoned}" /> </bean> <!-- 3. 启用自动扫描 --> <context:component-scan base-package="com.demo.*"> <!-- 排除注解为controller的类型 --> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" /> </context:component-scan> <!-- 4. 配置和mybatis的整合 --> <bean id = "sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 指定mybatis的全局文件位置 --> <property name="configLocation" value="classpath:mybatis.xml"/> <property name="dataSource" ref="dataSource"/> <!-- 指定mybatis的mapper文件 --> <property name="mapperLocations"> <list> <value>classpath:/mapper/*.xml</value> </list> </property> </bean> <!-- 5. 配置扫描器,将mybatis接口的实现加入到ioc容器中 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 扫描所有dao接口的实现,加入到 ioc容器中 --> <property name="basePackage" value="com.demo.dao"/> </bean> <!-- 6. 事务控制 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 控制住数据源 --> <property name="dataSource" ref="dataSource" /> </bean> <!-- 7. 开启基于注解的事务,使用xml配置形式的事务 --> <aop:config> <!-- 切入点表达式 --> <aop:pointcut expression="execution(* com.demo.service..*(..))" id="txPoint"/> <!-- 配置事务增强 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint"/> </aop:config> <!-- 8. 配置事务增强,也就是事务如何切入 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- 所有的方法都是事务方法 --> <tx:method name="*"/> <!-- 以get开始的所有方法 --> <tx:method name="get*" read-only="true"/> </tx:attributes> </tx:advice> <bean id = "springContextUtils" class="com.demo.common.utils.SpringContextUtils"/> <!--9. Quartz定时任务动态配置 --> <bean id = "schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"/></beans>
spring-mvc.xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd"> <!-- 1. 注解探测器 --> <context:component-scan base-package="com.demo.controller"> </context:component-scan> <!-- 2. 配置视图解析器,方便页面返回--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/page/"/> <property name="suffix" value=".jsp"/> </bean> <!-- 3.两个标准配置 --> <mvc:default-servlet-handler/> <mvc:annotation-driven/> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> <property name="messageConverters"> <list> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/> </list> </property> </bean></beans>
mybatis.xml
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration> <!-- 1. 驼峰规则命名 --> <settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> <!-- 2. 别名 --> <typeAliases> <typeAlias type="com.demo.domain.ScheduleJob" alias="ScheduleJob"/> </typeAliases> <!-- 3. 引入分页插件 --> <plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"/> </plugins></configuration>
web.xml
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>Archetype Created Web Application</display-name> <!-- 1. 启动spring容器 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 2. springmvc前端控制器,拦截所有请求 --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>*.action</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>*.json</url-pattern> </servlet-mapping> <!-- 3. 字符编码过滤器配置 ,字符过滤器要放在所有过滤器的前面--> <filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceRequestEncoding</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>forceResponseEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 5. 连接池启用Web监控统计功能start--> <filter> <filter-name>DruidWebStatFilter</filter-name> <filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class> <init-param> <param-name>exclusions</param-name> <param-value>*. js ,*. gif ,*. jpg ,*. png ,*. css ,*. ico ,/ druid /*</param-value> </init-param> </filter> <filter-mapping> <filter-name>DruidWebStatFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>DruidStatView</servlet-name> <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>DruidStatView</servlet-name> <url-pattern>/druid/*</url-pattern> </servlet-mapping> <!-- 连接池启用Web监控统计功能end--></web-app>
database.properties
#druid配置url:jdbc\:mysql\://localhost\:3306/quartz03?characterEncoding\=utf8&allowMultiQueries\=truedriverClassName:com.mysql.jdbc.Driverusername:rootpassword:adminfilters:statmaxActive:20initialSize:1maxWait:60000minIdle:10maxIdle:15timeBetweenEvictionRunsMillis:60000minEvictableIdleTimeMillis:300000validationQuery:SELECT 'x'testWhileIdle:truetestOnBorrow:falsetestOnReturn:falsemaxOpenPreparedStatements:20removeAbandoned:trueremoveAbandonedTimeout:1800logAbandoned:true
步骤3:前端页面
前端页面表格使用的bootstrap table,这里不再累述,页面样式如下:
index.jsp
<%-- Created by IntelliJ IDEA. User: Administrator Date: 2017/11/25 0020 Time: 下午 18:28 To change this template use File | Settings | File Templates.--%><%@ page contentType="text/html;charset=UTF-8" language="java" %><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %><%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %><c:set var="basePath" value="${pageContext.request.contextPath}" /><!DOCTYPE html><html><head> <title>定时任务</title> <link rel="stylesheet" href="${basePath}/static/bootstrap-dist/css/bootstrap.min.css"> <link rel="stylesheet" href="${basePath}/static/font-awesome/css/font-awesome.min.css"> <link rel="stylesheet" href="${basePath}/static/css/bootstrap-table.min.css"> <script src="${basePath}/static/js/jquery-2.0.3.min.js"></script> <script src="${basePath}/static/bootstrap-dist/js/bootstrap.min.js"></script> <script src="${basePath}/static/js/bootstrap-table.min.js"></script> <script src="${basePath}/static/js/bootstrap-table-zh-CN.js"></script></head><body><div id="rrapp" style="margin: 100px;"> <div id="showList"> <div class="grid-btn" style="height:34px;"> <a class="btn btn-primary" onclick="update();"><i class="fa fa-pencil-square-o"></i> 修改</a> <a class="btn btn-primary" onclick="pause();"><i class="fa fa-pause"></i> 暂停</a> <a class="btn btn-primary" onclick="resume();"><i class="fa fa-play"></i> 恢复</a> <a class="btn btn-primary" onclick="runOnce();"><i class="fa fa-arrow-circle-right"></i> 立即执行</a> </div> <table id="table"></table> </div> <!-- 修改时间模态框 --> <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> <h4 class="modal-title" id="myModalLabel">修改定时任务时间</h4> </div> <div class="modal-body"> <input type="text" id="modalId" style="display:none"/> Cron表达式 <input type="text" class="form-control" id="modalCron"> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> <button type="button" class="btn btn-primary" onclick="updateCron();">Save changes</button> </div> </div> </div> </div></div><script type="text/javascript"> $(function () { //初始化表格 initTable(); }); //修改按钮 function update() { //获取选中的行 var a = $("#table").bootstrapTable('getSelections'); if (a.length == 0) { alert("请先选中需要修改的项"); return false; } else if (a.length > 1) { alert("只能选择一项"); return false; } $("#modalId").val(a[0].id); $('#myModal').modal('toggle'); } //更新定时任务时间 function updateCron() { $('#myModal').modal('hide'); var id = $("#modalId").val(); var queryUrl = "${basePath}/scheduleJob/updateCron.do"; $.ajax({ type: 'POST', data: { id: id, cronExpression: $("#modalCron").val(), }, url: queryUrl, success: function (result) { //刷新表格 var opt = { url: '${basePath}/scheduleJob/listAllJob.do' }; $("#table").bootstrapTable('refresh', opt); } }) } //暂停一个定时任务 function pause() { var queryUrl = '${basePath}/scheduleJob/pauseJob.do'; commonSubmit(queryUrl); } //恢复一个定时任务 function resume() { var queryUrl = '${basePath}/scheduleJob/resumeJob.do'; commonSubmit(queryUrl); } //立即执行一个定时任务 function runOnce() { var queryUrl = "${basePath}/scheduleJob/runOnce.do"; commonSubmit(queryUrl); } //暂停、恢复、立即执行提交函数 function commonSubmit(queryUrl) { //获取选中的行 var a = $("#table").bootstrapTable('getSelections'); if (a.length == 0) { alert("请先选中需要修改的项"); return false; } else if (a.length > 1) { alert("只能选择一项"); return false; } var obj = a[0]; $.ajax({ type: 'post', data: { jobId: obj.id }, url: queryUrl, success: function (result) { //刷新表格,状态变更 var opt = { url: '${basePath}/scheduleJob/listAllJob.do' }; $("#table").bootstrapTable('refresh', opt); } }); } //表格详情 function initTable() { var queryUrl = '${basePath}/scheduleJob/listAllJob.do'; $('#table').bootstrapTable({ method: 'POST',//请求方式(*) contentType: "application/x-www-form-urlencoded;charset=UTF-8",//在服务端分页时必须配置 dataType: 'json', //toolbar: '#toolbar',//工具按钮用哪个容器 striped: true,//是否显示行间隔色 cache: false,//是否使用缓存,默认为true,所以一般情况下需要设置一下这个属性(*) pagination: true,//是否显示分页(*)、 onlyInfoPagination: false,//设置为true时只显示总数据,而不显示分页按钮 showPaginationSwitch: false, sortable: true,//是否启用排序 sortOrder: "asc",//排序方式 sidePagination: "server",//分页方式:client客户端分页,server服务端分页(*) pageNumber: 1,//初始化加载第一页,默认第一页,并记录 pageSize: 10,//每页的记录行数(*) pageList: [10, 25, 50, 100],//可供选择的每页的行数(*) url: queryUrl,//请求后台的URL(*) search: false,//是否显示表格搜索 strictSearch: true, showColumns: false,//是否显示所有的列(选择显示的列) showRefresh: false,//是否显示刷新按钮 minimumCountColumns: 1,//最少允许的列数 clickToSelect: true,//是否启用点击选中行 //height: 500, //行高,如果没有设置height属性,表格自动根据记录条数觉得表格高度 uniqueId: "ID",//每一行的唯一标识,一般为主键列 showToggle: false, //是否显示详细视图和列表视图的切换按钮 cardView: false,//是否显示详细视图 detailView: false,//是否显示父子表 paginationDetailHAlign: "left",//设置页面条数信息位置,默认在左边 showExport: false, //是否显示导出 exportDataType: "selected", //basic', 'all', 'selected'. //获取查询参数 queryParams: function queryParams(params) { //这里的键的名字和控制器的变量名必须一致,这边改动,控制器也需要改成一样的 var param = { pageSize: params.limit, //页面大小 pageNumber: (params.offset / params.limit) + 1, //页码 menuName: $("#menuNameQuery").val(), //菜单名称 parentName: $("#parentNameQuery").val(),//上级菜单名称 }; return param; }, columns: [ { field: 'Number', title: '', align: 'center', width: 20, formatter: function (value, row, index) { return index + 1; } }, { checkbox: true, visible: true //是否显示复选框 }, { field: 'id', title: '任务ID', width: 50, align: 'center' }, { field: 'jobName', title: 'JobName', width: 150, align: 'center' }, { field: 'jobGroup', title: 'JobGroup', width: 50, align: 'center' }, { field: 'beanClass', title: 'BeanClass', align: 'center' }, { field: 'methodName', title: 'MethodName', width: 100, align: 'center' }, { field: 'params', title: '参数', width: 100, align: 'center' }, { field: 'cronExpression', title: 'cron表达式', width: 100, align: 'center' }, { field: 'status', title: '状态', width: 50, align: 'center', formatter: function (value, row, index) { if (value == 0) { return "<a href='javascript:void(0);' class='btn btn-primary btn-xs'>正常</a>"; } if (value == 1) { return "<a href='javascript:void(0);' class='btn btn-danger btn-xs'>暂停</a>"; } } }, { field: 'remark', title: '备注', width: 100, align: 'center' }], }); }</script></body></html>
步骤4:定时任务的启动、暂停、时间修改、立即执行
在数据库中我们有设计字段status表示定时任务的状态,0表示正常状态,1表示暂停状态
启动服务器时需要启动status为1的定时任务
4.1 项目启动时启动定时任务
在service在中添加初始化方法,在方法前添加@PostConstruct注解。在方法里面添加调用定时任务的方法
被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器调用一次,类似于Serclet的init()方法
4.2 定时任务的时间修改、暂停、立即执行
定时任务的修改、暂停主要是调用quartz内置方法pauseJob()、resumeJob()、triggerJob()等方法
//暂停一个job JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()); scheduler.pauseJob(jobKey);// 恢复一个定时任务JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());scheduler.resumeJob(jobKey);// 立即执行一个定时任务JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());scheduler.triggerJob(jobKey);// 更新时间表达式TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression());trigger = trigger.getTriggerBuilder() .withIdentity(triggerKey) .build();scheduler.rescheduleJob(triggerKey,trigger);
ScheduleJobServiceImpl
package com.demo.service.impl;import com.demo.common.result.BootstrapTableResult;import com.demo.common.result.Constant;import com.demo.dao.ScheduleJobMapper;import com.demo.domain.ScheduleJob;import com.demo.schedule.QuartzJobFactory;import com.demo.service.ScheduleJobService;import com.github.pagehelper.PageHelper;import com.github.pagehelper.PageInfo;import org.quartz.*;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Service;import javax.annotation.PostConstruct;import javax.annotation.Resource;import java.util.List;/** * @author Administrator * @date 2017-11-21 上午 9:36 */@Service("scheduleJobService")public class ScheduleJobServiceImpl implements ScheduleJobService { private Logger log = LoggerFactory.getLogger(ScheduleJobServiceImpl.class); @Resource private ScheduleJobMapper scheduleJobMapper; @Resource private Scheduler scheduler; public Scheduler getScheduler() { return scheduler; } public void setScheduler(Scheduler scheduler) { this.scheduler = scheduler; } /** * 项目启动时初始化定时器 */ @PostConstruct public void init() { //获取所有的定时任务 List<ScheduleJob> scheduleJobList = scheduleJobMapper.listAllJob(); if (scheduleJobList.size() != 0) { for (ScheduleJob scheduleJob : scheduleJobList) { addJob(scheduleJob); } } } /** * 添加任务 * @param job */ private void addJob(ScheduleJob job) { try { log.info("初始化"); TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName(), job.getJobGroup()); CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); //不存在,则创建 if (null == trigger) { Class clazz = QuartzJobFactory.class; JobDetail jobDetail = JobBuilder. newJob(clazz). withIdentity(job.getJobName(), job.getJobGroup()). build(); jobDetail.getJobDataMap().put("scheduleJob", job); CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression()); //withIdentity中写jobName和groupName trigger = TriggerBuilder. newTrigger(). withIdentity(job.getJobName(), job.getJobGroup()) .withSchedule(scheduleBuilder) .build(); scheduler.scheduleJob(jobDetail, trigger); //如果定时任务是暂停状态 if(job.getStatus() == Constant.STATUS_NOT_RUNNING){ pauseJob(job.getId()); } } else { // Trigger已存在,那么更新相应的定时设置 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression()); // 按新的cronExpression表达式重新构建trigger trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build(); // 按新的trigger重新设置job执行 scheduler.rescheduleJob(triggerKey, trigger); } } catch (Exception e) { log.error("添加任务失败", e); } } /** * 查询所有的定时任务 * @return BootstrapTableResult */ @Override public BootstrapTableResult listAllJob(int pageSize, int pageNumber) { PageHelper.startPage(pageNumber, pageSize); List<ScheduleJob> scheduleJobList = scheduleJobMapper.listAllJob(); PageInfo pageInfo = new PageInfo(scheduleJobList, Constant.PAGENUMBER); int total = (int) pageInfo.getTotal(); BootstrapTableResult bootstrapTableResult = new BootstrapTableResult(total, scheduleJobList); return bootstrapTableResult; } /** * 暂停定时任务 * @param jobId */ @Override public void pauseJob(int jobId) { //修改定时任务状态 ScheduleJob scheduleJob = getScheduleJobByPrimaryKey(jobId); scheduleJob.setId(jobId); scheduleJob.setStatus(Constant.STATUS_NOT_RUNNING); updateJobStatusById(scheduleJob); try { //暂停一个job JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()); scheduler.pauseJob(jobKey); }catch (Exception e){ log.error("CatchException:暂停任务失败",e); } } /** * 恢复一个定时任务 * @param jobId */ @Override public void resumeJob(int jobId) { //修改定时任务状态 ScheduleJob scheduleJob = getScheduleJobByPrimaryKey(jobId); scheduleJob.setStatus(Constant.STATUS_RUNNING); updateJobStatusById(scheduleJob); try{ //恢复一个定时任务 JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()); scheduler.resumeJob(jobKey); }catch (Exception e){ log.error("CatchException:恢复定时任务失败",e); } } /** * 立即执行一个定时任务 * @param jobId */ @Override public void runOnce(int jobId) { try{ ScheduleJob scheduleJob = getScheduleJobByPrimaryKey(jobId); JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()); scheduler.triggerJob(jobKey); }catch (Exception e){ log.error("CatchException:恢复定时任务失败",e); } } /** * 更新时间表达式 * @param id * @param cronExpression */ @Override public void updateCron(int id, String cronExpression) { ScheduleJob scheduleJob = getScheduleJobByPrimaryKey(id); scheduleJob.setCronExpression(cronExpression); updateJobCronExpressionById(scheduleJob); try { TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()); CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression()); trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build(); scheduler.rescheduleJob(triggerKey,trigger); }catch(Exception e){ log.error("CatchException:更新时间表达式失败",e); } } /** * 修改定时任务状态 * @param scheduleJob */ private void updateJobStatusById(ScheduleJob scheduleJob){ scheduleJobMapper.updateJobStatusById(scheduleJob); } /** * 修改定时任务时间 */ private void updateJobCronExpressionById(ScheduleJob scheduleJob){ scheduleJobMapper.updateJobCronExpressionById(scheduleJob); } /** * 通过主键id查找定时任务 * @param id * @return ScheduleJob */ private ScheduleJob getScheduleJobByPrimaryKey(int id){ return scheduleJobMapper.getScheduleJobByPrimaryKey(id); }}
QuartzJobFactory.java
package com.demo.schedule;import com.demo.common.utils.TaskUtils;import com.demo.domain.ScheduleJob;import org.quartz.Job;import org.quartz.JobExecutionContext;import org.quartz.JobExecutionException;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * @author admin * @date 2017-11-25 下午 21:49 */public class QuartzJobFactory implements Job { public Logger log = LoggerFactory.getLogger(this.getClass()); @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println("定时任务运行中..."); ScheduleJob scheduleJob = (ScheduleJob) jobExecutionContext.getMergedJobDataMap().get("scheduleJob"); TaskUtils.invokeMethod(scheduleJob); }}
执行计划任务的代码就在TaskUtils.invokMethod(scheduleJob)里面,通过scheduleJob的beanClass来获得需要执行的类,通过methodName来确定执行哪个方法
定时任务启动后,可以直接修改启动、暂停状态,但是重启服务器时并不会记录当前状态,建议先将修改后的状态保存到数据库status字段中
TaskUtils.java
package com.demo.common.utils;import com.demo.domain.ScheduleJob;import org.apache.commons.lang.StringUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;/** * @author admin * @date 2017-11-21 下午 16:07 */public class TaskUtils { public static Logger log = LoggerFactory.getLogger(TaskUtils.class); public static void invokeMethod(ScheduleJob scheduleJob) { Object object = null; Class clazz = null; if (StringUtils.isNotBlank(scheduleJob.getBeanClass())) { try { clazz = Class.forName(scheduleJob.getBeanClass()); object = clazz.newInstance(); } catch (Exception e) { log.error("CatchException:",e); } } if (object == null) { log.error("任务名称 = [" + scheduleJob.getJobName() + "]---------------未启动成功,请检查是否配置正确!!!"); System.out.println("任务名称 = [" + scheduleJob.getJobName() + "]---------------未启动成功,请检查是否配置正确!!!"); return; } clazz = object.getClass(); Method method = null; try { method = clazz.getDeclaredMethod(scheduleJob.getMethodName()); } catch (NoSuchMethodException e) { log.error("任务名称 = [" + scheduleJob.getJobName() + "]---------------未启动成功,方法名设置错误!!!"); System.out.println("任务名称 = [" + scheduleJob.getJobName() + "]---------------未启动成功,方法名设置错误!!!"); } catch (SecurityException e) { e.printStackTrace(); } if (method != null) { try { method.invoke(object); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } log.info("任务名称 = [" + scheduleJob.getJobName() + "]----------启动成功"); }}
SpringContextUtil.java
package com.demo.common.utils;import org.springframework.beans.BeansException;import org.springframework.beans.factory.NoSuchBeanDefinitionException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component;/** * 获取spring容器,以访问容器中定义的其他bean * @author Administrator * @date 2017-11-21 下午 12:39 */@Componentpublic class SpringContextUtils implements ApplicationContextAware { /** * Spring应用上下文环境 */ public static ApplicationContext applicationContext; /** * 实现ApplicationContextAware接口的回调方法,设置上下文环境 * @param applicationContext * @throws BeansException */ @Override public void setApplicationContext(ApplicationContext applicationContext) { SpringContextUtils.applicationContext = applicationContext; } /** * 获取对象 这里重写了bean方法,起主要作用 * @param name * @return Object 一个以所给名字注册的bean的实例 * @throws BeansException */ public static <T> T getBean(String name) throws BeansException { return (T) applicationContext.getBean(name); } /** * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true * @param name * @return boolean */ public static boolean containsBean(String name) { return applicationContext.containsBean(name); } /** * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 * 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) * @param name * @return boolean * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException */ public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException { return applicationContext.isSingleton(name); } /** * @param name * @return Class 注册对象的类型 * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException * */ public static Class<? extends Object> getType(String name) throws NoSuchBeanDefinitionException { return applicationContext.getType(name); } public static String[] getAliases(String name) throws NoSuchBeanDefinitionException { return applicationContext.getAliases(name); }}
4.3 需要执行的定时任务
TaskTest1.java
package com.demo.schedule;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import java.nio.charset.Charset;import java.util.Map;/** * @author admin * @date 2017-11-25 下午 20:14 */@Componentpublic class TaskTest1 { public static final Logger LOGGER = LoggerFactory.getLogger(TaskTest1.class); public void run1(){ System.out.println("执行方法1"); } public void run2(){ System.out.println("执行方法2"); } public void run3(){ System.out.println("执行方法3"); } public void run4(){ System.out.println("执行方法4"); }}
4.4 项目中涉及到的其他文件
Constant.java
/** * 常量类 * @author admin * @date 2017-11-25 19:06 */public class Constant { /** * 分页条显示页数 */ public static final int PAGENUMBER = 5; /** * 定时任务启动状态 */ public static final int STATUS_RUNNING = 0; /** * 定时任务暂停状态 */ public static final int STATUS_NOT_RUNNING = 1;}
BootstrapTableResult.java
/** * bootstrapTable所需的结果集 * @author admin * @date 2017-11-25 18:59 */ public class BootstrapTableResult { /** * 总记录数 */ private Integer total; /** * 结果集的list集合 */ private List rows; 省略get/set...}
BaseResult.java
/** * 统一返回结果类 * @author Administrator * @date 2017-11-25 下午 20:07 */public class BaseResult { /** * 状态码:1成功,其他为失败 */ private int code; /** * 成功为success,其他为失败原因 */ private String message; /** * 数据结果集 */ public Object data; 省略get/set
ScheduleJob实体类
/** * 定时任务实体类 * @author admin * @date 2017-11-25 下午 19:06 */public class ScheduleJob { /** * 主键id */ private Integer id; /** * 任务名 */ private String jobName; /** * 任务组 */ private String jobGroup; /** * 要执行的方法的名称 */ private String methodName; /** * 要执行的方法所在的class路径 */ private String beanClass; /** * 定时任务状态,0表示正常,1表示停止 */ private Integer status; /** * 时间表达式 */ private String cronExpression; /** * 参数 */ private String params; /** * 备注 */ private String remark; /** * 创建时间 */ private Date createTime; /** * 修改时间 */ private Date modifyTime; 省略get/set}
Controller层:ScheduleJobController.java
package com.demo.controller;import com.demo.common.result.BaseResult;import com.demo.common.result.BootstrapTableResult;import com.demo.service.ScheduleJobService;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.ResponseBody;import javax.annotation.Resource;/** * 定时任务控制层 * @author admin * @date 2017-11-25 18:49 */@Controller@RequestMapping(value = "/scheduleJob")public class ScheduleJobController { public static Logger log = LoggerFactory.getLogger(ScheduleJobController.class); @Resource private ScheduleJobService scheduleJobService; /** * 查询所有的定时任务,用于页面加载时显示表格数据 * @param pageSize 每页显示数量 * @param pageNumber 页数 * @return BootstrapTableResult */ @RequestMapping(value = "/listAllJob", method = RequestMethod.POST) @ResponseBody public BootstrapTableResult listAllJob(int pageSize, int pageNumber) { BootstrapTableResult bootstrapTableResult = scheduleJobService.listAllJob(pageSize, pageNumber); return bootstrapTableResult; } /** * 暂停定时任务 * @param jobId * @return BaseResult */ @RequestMapping(value = "/pauseJob", method = RequestMethod.POST) @ResponseBody public BaseResult pauseJob(int jobId) { scheduleJobService.pauseJob(jobId); return new BaseResult(1, "success", "定时任务暂停成功"); } /** * 恢复定时任务 * @param jobId * @return BaseResult */ @RequestMapping(value="/resumeJob",method = RequestMethod.POST) @ResponseBody public BaseResult resumeJob(int jobId){ scheduleJobService.resumeJob(jobId); return new BaseResult(1, "success", "定时任务恢复成功"); } /** * 立即执行定时任务 * @param jobId * @return BaseResult */ @RequestMapping(value = "/runOnce",method = RequestMethod.POST) @ResponseBody public BaseResult runOnce(int jobId){ scheduleJobService.runOnce(jobId); return new BaseResult(1, "success", "立即执行定时任务成功"); } /** * 更新时间表达式 * @param id * @param cronExpression * @return BaseResult */ @RequestMapping(value = "/updateCron",method = RequestMethod.POST) @ResponseBody public BaseResult updateCron(int id,String cronExpression){ scheduleJobService.updateCron(id,cronExpression); return new BaseResult(1, "success", "更新时间表达式成功"); }}
dao层:ScheduleJobMapper.java
package com.demo.dao;import com.demo.domain.ScheduleJob;import java.util.List;/** * 定时任务 * @author admin * @date 2017-11-20 下午 15:52 */public interface ScheduleJobMapper { /** * 查询所有的定时任务 * @return List<ScheduleJob> */ List<ScheduleJob> listAllJob(); /** * 更新定时任务状态 * @param scheduleJob */ void updateJobStatusById(ScheduleJob scheduleJob); /** * 根据主键查询定时任务 * @param id * @return ScheduleJob */ ScheduleJob getScheduleJobByPrimaryKey(int id); /** * 更新时间表达式 * @param scheduleJob */ void updateJobCronExpressionById(ScheduleJob scheduleJob);}
dao实现层:ScheduleJobMapper.xml
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.demo.dao.ScheduleJobMapper"> <resultMap id="BaseResultMap" type="com.demo.domain.ScheduleJob"> <id column="id" property="id" /> <result column="job_name" property="jobName" /> <result column="job_group" property="jobGroup" /> <result column="method_name" property="methodName" /> <result column="bean_class" property="beanClass" /> <result column="status" property="status" /> <result column="cron_expression" property="cronExpression" /> <result column="params" property="params" /> <result column="remark" property="remark" /> <result column="create_time" property="createTime" /> <result column="modify_time" property="modifyTime" /> </resultMap> <sql id="Base_Column_List"> id, job_name, job_group, method_name, bean_class, status, cron_expression, params, remark, create_time, modify_time </sql> <!-- 查询所有的定时任务 --> <select id="listAllJob" resultMap="BaseResultMap"> select <include refid="Base_Column_List" /> from schedule_job </select> <!-- 更新定时任务状态 --> <update id="updateJobStatusById" parameterType="ScheduleJob"> update schedule_job SET status = #{status} where id = #{id} </update> <!-- 根据主键查询定时任务 --> <select id = "getScheduleJobByPrimaryKey" resultMap="BaseResultMap"> SELECT * from schedule_job WHERE id = #{id} </select> <!-- 修改定时任务时间表达式 --> <update id="updateJobCronExpressionById" parameterType="ScheduleJob"> UPDATE schedule_job SET cron_expression = #{cronExpression} where id = #{id} </update></mapper>
service层:ScheduleJobService.java
package com.demo.service;import com.demo.common.result.BootstrapTableResult;import com.demo.dao.ScheduleJobMapper;import org.quartz.Scheduler;import org.springframework.stereotype.Service;import javax.annotation.Resource;/** * @author admin * @date 2017-11-25 18:51 */public interface ScheduleJobService { /** * 查询所有的定时任务 * @param pageSize * @param pageNumber * @return BootstrapTableResult */ BootstrapTableResult listAllJob(int pageSize, int pageNumber); /** * 暂停定时任务 * @param jobId */ void pauseJob(int jobId); /** * 恢复一个定时任务 * @param jobId */ void resumeJob(int jobId); /** * 立即执行一个定时任务 * @param jobId */ void runOnce(int jobId); /** * 更新时间表达式 * @param id * @param cronExpression */ void updateCron(int id, String cronExpression);}
步骤5. 测试
在数据库中添加以下数据
第一条每5秒执行一次,第二条数据每秒执行一次,第三条和第四条数据启动时不执行
启动服务器,控制台输出如下结果
修改定时任务1的时间,设置为5秒,查看控制台,可以看到定时任务1和定时任务2都是每5秒执行一次
暂定、恢复、立即执行这里就不一一测试了,有需要的自行测试(注:立即执行只会执行一次)
源码下载地址:https://gitee.com/seek412/quartz03.git
上一篇:spring整合quartz定时任务(附demo)(二)
- quartz定时任务动态配置详细教程(附demo)(三)
- Spring 整合 Quartz 实现动态定时任务(附demo)
- Quartz定时任务(附demo)(一)
- spring整合quartz定时任务(附demo)(二)
- quartz 配置 动态定时任务
- 定时任务Quartz超详细教程
- 任务调度Quartz初探Demo(三)
- Spring+quartz定时任务(配置)
- quartz配置发布定时任务(二)
- quartz(定时任务)
- Spring Quartz 动态配置定时任务
- Spring Quartz 动态配置定时任务
- Spring Quartz 动态配置定时任务
- Quartz实现数据库动态配置定时任务
- 使用Quartz配置动态定时任务(总结)
- quartz动态定时任务
- spring quartz 定时任务 demo
- 定时任务,quartz的demo
- 职业发展一百问之第四问:技术还是管理
- 多线程Thread(Runnable target)
- linux下如何杀掉D状态进程
- SpringCloud学习组件和概念
- 节点一复习-表单的创建和基本控件
- quartz定时任务动态配置详细教程(附demo)(三)
- java.lang.IllegalArgumentException: findUserById is ambiguous in Mapped Statements collection
- 结构化程序设计方法
- redis笔记
- Json简介及fastJson使用
- 【数据结构】内部排序算法
- 策略模式
- Android 广播详解
- 【Day02】Aop注解和Aspectj注解+JdbcTemplate