【Java EE 学习 77 上】【数据采集系统第九天】【通过AOP实现日志管理】【通过Spring石英调度动态生成日志表】【日志分表和查询】
一、需求分析
日志数据在很多行业中都是非常敏感的数据,它们不能删除只能保存和查看,这样日志表就会越来越大,我们不可能永远让它无限制的增长下去,必须采取一种手段将数据分散开来。假设现在整个数据库需要保存的数据量比较少,但是只有日志表的数据量会很大,在这种情况下我们可以考虑使用分表策略分散保存日志数据。
针对当前系统来讲,可以这么做:每个月创建一张新表用于保存当月的日志数据。当然这只是初期的保存日志的思路。
1.解决问题的方法就是分表,那么什么时候创建新表呢?
(1).如果服务器不关闭,假设一直处于运行状态,每个月的月末创建下一个月的日志表好像是比较不错的,但是这和软件测试的边界值条件相符合,是非常容易出错的地方,所以,在每个月的中间创建新表是比较不错的选择;处于系统的健壮性考虑,创建接下来两个月的表是比较合适的。
(2).当前月的表如何创建。
服务器不可能一开始就是开启的,所以我们需要在服务器开启的时候就创建好当前月的日志表,但是只是创建当前月的日志表还是不够的,还需要创建接下来两个月使用的日志表,或许你会问为什么,之后就交给某个定时器每个月中间自动创建表不就可以了吗?但是你没有考虑到,如果服务器的启动时间正好是在一个月的下半个月怎么办?这时候到了下个月的时候就没有日志表可用了。
2.接下来还有问题就是怎么将日志信息保存到当前月的日志表
这个实际上是比较简单的,我们通过一个规则根据时间动态的指定创建的表名,同样在插入数据的时候也能够使用相同的规则插入到当前月的表中。
3.怎么将数据从多个表中取出来
使用sql的union连接查询即可达到目的。
二、使用spring的石英调度定时创建日志表
使用spring石英调度任务的步骤如下:
1.创建石英调度任务类
该类必须继承org.springframework.scheduling.quartz.QuartzJobBean抽象类并重写executeInternal方法
package com.kdyzm.schedual; import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean; import com.kdyzm.service.LogService;
import com.kdyzm.utils.LogUtils;
/**
* 创建的石英任务:使用spring集成的石英调度,动态生成日志表
* @author kdyzm
*
*/
public class GenerateLogsTableTask extends QuartzJobBean{
private LogService logService;
public LogService getLogService() {
return logService;
}
public void setLogService(LogService logService) {
this.logService = logService;
}
/**
* 执行调度任务的方法
* 每月15号创建下两个月需要用到的日志表
*/
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
String tableName=LogUtils.createGenerateLogsTableName(1);
String sql="create table if not exists "+tableName+" like logs";
this.logService.executeSql(sql);
System.out.println(tableName+" 表生成了!");
tableName=LogUtils.createGenerateLogsTableName(2);
sql="create table if not exists "+tableName+" like logs";
this.logService.executeSql(sql);
System.out.println(tableName+" 表生成了!");
}
}
这里需要创建一个LogUtils工具类并且封装一个生成日志表名的方法:该方法的参数是一个偏移量,如果是整数表示下几个月,如果是负数表示是上几个月,使用Calendar类给出的方法能够非常快速的计算出来加上几个月或者减去几个月之后的日期。
package com.kdyzm.utils; import java.util.Calendar; /**
* 专门针对日志生成流程定义的工具类
* @author kdyzm
*
*/
public class LogUtils {
//动态生成日志表名的方法
public static String createGenerateLogsTableName(int offset){
Calendar calendar=Calendar.getInstance();
// month=(month+offset-1)%month+1;
//计算偏移之后的动态表名
calendar.add(Calendar.MONTH, offset);
int year=calendar.get(Calendar.YEAR);
int month=calendar.get(Calendar.MONTH)+1;
return "logs_"+year+"_"+month;
}
}
2.配置applicationConext.xml
为了更加清晰的完成该项任务,单独使用一个配置文件完成该项任务的配置。
配置步骤:
(1)使用org.springframework.scheduling.quartz.JobDetailBean封装石英任务
<bean id="jobDetailBean" class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass" value="com.kdyzm.schedual.GenerateLogsTableTask"></property>
<!-- 通过spring管理的bean必须通过这种方式注入到schema中 -->
<property name="jobDataAsMap">
<map>
<entry key="logService" value-ref="logService"></entry>
</map>
</property>
</bean>
(2)设置触发器Bean,设置任务的调度策略
<!-- 触发器bean,设置任务的调度策略 -->
<bean id="cronTriggerBean" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail" ref="jobDetailBean"></property>
<property name="cronExpression">
<!-- 这个表达式的意思是:每个月的15号 -->
<value>0 0 0 15 * ? *</value>
</property>
</bean>
这里cronExpression中的值怎么填写是比较重要的,这里有7个参数需要填写,必须明白这七个参数的意思是什么
这七个参数分别对应着 [秒] [分] [小时] [日] [月] [周] [年]
其中"日"和"周"两个字段是相互对立的两个字段,"日"值的是一个月的几号,"周"指的是一周的星期几,两者是"有你无我"的立场。
序号 |
说明 |
是否必填 |
允许填写的值 |
允许的通配符 |
1 |
秒 |
是 |
0-59 |
, - * / |
2 |
分 |
是 |
0-59 |
, - * / |
3 |
小时 |
是 |
0-23 |
, - * / |
4 |
日 |
是 |
1-31 |
, - * ? / L W |
5 |
月 |
是 |
1-12 or JAN-DEC |
, - * / |
6 |
周 |
是 |
1-7 or SUN-SAT |
, - * ? / L # |
7 |
年 |
否 |
empty 或 1970-2099 |
, - * / |
(3)使用调度工厂bean激活触发器,启动石英任务
<!-- 调度器工厂bean,激活触发器,启动石英任务的 -->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<ref bean="cronTriggerBean"/>
</property>
</bean>
从上述三个步骤来看,后一个步骤依次对前面的任务进行了封装。
完整的schedual.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans file:///D:\程序\java\Spring\spring-framework-4.2.1\spring-framework-4.2.1.RELEASE\schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context file:///D:\程序\java\Spring\spring-framework-4.2.1\spring-framework-4.2.1.RELEASE\schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop file:///D:\程序\java\Spring\spring-framework-4.2.1\spring-framework-4.2.1.RELEASE\schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx file:///D:\程序\java\Spring\spring-framework-4.2.1\spring-framework-4.2.1.RELEASE\schema/tx/spring-tx-2.5.xsd">
<!-- 配置调度任务的spring配置文件 --> <!-- 任务明细bean,对石英任务进行封装 -->
<bean id="jobDetailBean" class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass" value="com.kdyzm.schedual.GenerateLogsTableTask"></property>
<!-- 通过spring管理的bean必须通过这种方式注入到schema中 -->
<property name="jobDataAsMap">
<map>
<entry key="logService" value-ref="logService"></entry>
</map>
</property>
</bean>
<!-- 触发器bean,设置任务的调度策略 -->
<bean id="cronTriggerBean" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail" ref="jobDetailBean"></property>
<property name="cronExpression">
<!-- 这个表达式的意思是:每个月的15号 -->
<value>0 0 0 15 * ? *</value>
</property>
</bean>
<!-- 调度器工厂bean,激活触发器,启动石英任务的 -->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<ref bean="cronTriggerBean"/>
</property>
</bean>
</beans>
schedual.xml
3.配置web.xml配置文件
由于配置文件是单独的配置文件,所以需要在web.xml配置文件中单独声明:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/applicationContext.xml,classpath:spring/schedual.xml</param-value>
</context-param>
三、使用Spring监听器动态创建当前月和接下来两个月需要用到的日志表。
这和初始化权限表的的流程几乎完全相同,只是不需要配置applicationContext.xml配置文件了,只需要给监听器类加上注解纳入spring管理即可。
package com.kdyzm.listener; import javax.annotation.Resource; import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component; import com.kdyzm.service.LogService;
import com.kdyzm.utils.LogUtils;
/**
* 动态生成当前月份的日志表
* 这里一次性生成三个月份的日志表,防止服务器启动的时间是在一个月的下半个月,即在15号之后
* @author kdyzm
*
*/
@Component
public class InitLogTableListener implements ApplicationListener{
@Resource(name="logService")
private LogService logService;
@Override
public void onApplicationEvent(ApplicationEvent event) {
if(event instanceof ContextRefreshedEvent){
//生成当前月的日志表
String tableName=LogUtils.createGenerateLogsTableName(0);
String sql="create table if not exists "+tableName+" like logs";
logService.executeSql(sql);
System.out.println(tableName+" 表已经生成!"); //生成下一个月的日志表
tableName=LogUtils.createGenerateLogsTableName(1);
sql="create table if not exists "+tableName+" like logs";
logService.executeSql(sql);
System.out.println(tableName+" 表已经生成!"); //生成第二个月的日志表
tableName=LogUtils.createGenerateLogsTableName(2);
sql="create table if not exists "+tableName+" like logs";
logService.executeSql(sql);
System.out.println(tableName+" 表已经生成!");
}
}
}
这时候Service中必须提供执行SQL语句的方法,使用hibernate中的SQLQuery对象即可完成该项任务,略。
完成二、三中的任务之后,动态创建当前月份的日志表的任务和定时创建后两个月的日志表的功能就已经实现了:启动tomcat服务器的时候就能够发现创建三张表的日志信息,同时每个月的14号00:00就会发现创建两个表的日志信息。
四、保存日志到日志分表
之前保存日志的时候保存到的表是log表,现在的任务是需要将日志保存到当前月对应的表中。这就需要考虑从哪里实现该该方法比较好,能够改动最小的代码实现该功能。
回顾Service,Service调用saveEntity方法保存日志对象,实际上调用的方法是LogDaoImpl对象中的方法,由于该类中没有定义saveEntity方法,所以会自动到父类中查找是否有该方法,结果在BaseDaoImpl类中找到了该方法,所以就调用了该方法,采用的泛型是Log,所以保存到了log表中。基于该流程,最合适的地方只有两个,一个是Service中国的save方法,另一个就是DAO中的save方法,综合考虑还是调用DAO中的方法,在LogDaoImpl类中重写BaseDaoImpl中的saveEntity方法,这样Service中的代码根本不需要改变就能够实现预定的目标了。
package com.kdyzm.dao.impl; import java.util.Collection; import org.springframework.stereotype.Repository; import com.kdyzm.dao.base.impl.BaseDaoImpl;
import com.kdyzm.domain.Log;
import com.kdyzm.utils.LogUtils;
import com.kdyzm.utils.StringUtils;
@Repository("logDao")
public class LogDaoImpl extends BaseDaoImpl<Log>{
/**
* 重写父类BaseDaoImpl中的方法,这里要动态指定表名,所以不能再使用hibernate提供的保存数据的方法
* 直接使用原生的slq语句来保存即可
*/
@Override
public void saveEntity(Log log) {
String tableName=LogUtils.createGenerateLogsTableName(0);
String sql="insert into "+tableName+" ("
+ " logId,operateParams,operateResult,operator,operatorDate,operatorName,resultMessage) "
+ "values (?,?,?,?,?,?,?)";
this.executeSql(sql,
StringUtils.getUUIDString(),
log.getOperateParams(),
log.getOperateResult(),
log.getOperator(),
log.getOperatorDate(),
log.getOperatorName(),
log.getResultMessage()
);
}
}
五、读取日志
由于日志已经分散到了多个表中,如果想要获取指定的日志数据,就必须到多个表中查询日志数据,这样就需要使用到了union关键字;和之前的保存日志的遇到的问题相同,之前查询所有日志使用的表是log表,现在需要查询当前月份对应的表,处理方式和之前保存日志数据使用的方式相同,只需要重写LogDaoImpl中的findAllEntities方法即可,默认查询当前月和上一个月的日志表(其实本应该指定范围的,暂时化简一下处理过程,只是查询当前月份的日志数据和上一个月份的日志数据),这样最终LogDaoImpl的形态就变成了这样:
package com.kdyzm.dao.impl; import java.util.Collection; import org.springframework.stereotype.Repository; import com.kdyzm.dao.base.impl.BaseDaoImpl;
import com.kdyzm.domain.Log;
import com.kdyzm.utils.LogUtils;
import com.kdyzm.utils.StringUtils;
@Repository("logDao")
public class LogDaoImpl extends BaseDaoImpl<Log>{
/**
* 重写父类中的方法,这里要动态指定表名,所以不能再使用hibernate提供的保存数据的方法
* 直接使用原生的slq语句来保存即可
*/
@Override
public void saveEntity(Log log) {
String tableName=LogUtils.createGenerateLogsTableName(0);
String sql="insert into "+tableName+" ("
+ " logId,operateParams,operateResult,operator,operatorDate,operatorName,resultMessage) "
+ "values (?,?,?,?,?,?,?)";
this.executeSql(sql,
StringUtils.getUUIDString(),
log.getOperateParams(),
log.getOperateResult(),
log.getOperator(),
log.getOperatorDate(),
log.getOperatorName(),
log.getResultMessage()
);
}
//重写该方法,因为该方法必须实现多表联合查询
@Override
public Collection<Log> findAllEntities() {
String tableName=LogUtils.createGenerateLogsTableName(0);//当前月的日志表
String talbeName1=LogUtils.createGenerateLogsTableName(-1);//上个月的日志表
String sql="select * from "+tableName+" union select * from "+talbeName1+" order by operatorDate desc";
return this.findAllEntitiesBySql(sql);
}
}
最终的效果和之前未做分表处理的效果完全相同。
【Java EE 学习 77 上】【数据采集系统第九天】【通过AOP实现日志管理】【通过Spring石英调度动态生成日志表】【日志分表和查询】的更多相关文章
- 【Java EE 学习 77 下】【数据采集系统第九天】【使用spring实现答案水平分库】【未解决问题:分库查询问题】
之前说过,如果一个数据库中要存储的数据量整体比较小,但是其中一个表存储的数据比较多,比如日志表,这时候就要考虑分表存储了:但是如果一个数据库整体存储的容量就比较大,该怎么办呢?这时候就需要考虑分库了, ...
- 【Java EE 学习 74 上】【数据采集系统第六天】【使用Jfreechart的统计图实现】【Jfreechart的基本使用方法】
之前已经实现了数据的采集,现在已经有了基本的数据,下一步就需要使用这些数据实现统计图的绘制了.这里使用Jfreechart实现这些统计图的绘制.首先看一下Jfreechart的基本用法,只有知道了它的 ...
- 【Java EE 学习 80 上】【WebService】
一.WebService概述 什么是WebService,顾名思义,就是基于Web的服务,它使用Http方式接收和响应外部系统的某种请求,从而实现远程调用.WebService实际上就是依据某些标准, ...
- 【Java EE 学习 25 上】【网上图书商城项目实战】
一.概述 1.使用的jdk版本:1.6 2.java EE版本:1.6 3.指导老师:传智播客 王建 二.小项目已经实现的功能 普通用户: 1.登陆 2.注册 3.购物 4.浏览 管理员用户(全部管理 ...
- 【Java EE 学习 29 上】【PL/SQL】【存储过程】【存储函数】【触发器】
一.PL/SQL简介 1.概念:PL/SQL语言是Oracle数据库专用的一种高级程序设计语言,是对标准SQL语言进行了过程化扩展的语言. 2.功能:既能够实现对数据库的操作,也能够通过过程化语言中的 ...
- 【Java EE 学习 28 上】【oracle学习第二天】【子查询】【集合运算】【几种数据库对象】
一.子查询 1.为什么要使用子查询:问题不能一步求解或者一个查询不能通过一步查询得到. 2.分类:单行子查询和多行子查询. 3.子查询的本质:一个查询中包含了另外一个或者多个查询. 4.使用子查询的规 ...
- 【Java EE 学习 78 上】【数据采集系统第十天】【Service使用Spring缓存模块】
一.需求分析 调查问卷中或许每一个单击动作都会引发大量的数据库访问,特别是在参与调查的过程中,只是单击“上一页”或者“下一页”的按钮就会引发大量的查询,必须对这种问题进行优化才行.使用缓存策略进行查询 ...
- 【Java EE 学习 76 上】【数据采集系统第八天】【角色授权】【用户授权】【权限的粗粒度控制】【权限的细粒度控制】
一.角色管理 单击导航栏上的"角色管理"超链接,跳转到角色管理界面,在该界面上显示所有角色,并提供角色的增加和删除.修改超链接. 1.增加新角色(角色授权) 流程:单击增加新角色超 ...
- 【Java EE 学习 72 上】【数据采集系统第四天】【增加调查logo】【文件上传】【动态错误页指定】【上传限制】【国际化】
增加logo的技术点:文件上传,国际化 文件上传的功能在struts2中是使用文件上传拦截器完成的. 1.首先需要在页面上添加一个文件上传的超链接. 点击该超链接能够跳转到文件上传页面.我给该表单页面 ...
随机推荐
- web页面之响应式布局
一.什么是响应式布局? 响应式布局是Ethan Marcotte在2010年5月份提出的一个概念,简而言之,就是一个网站能够兼容多个终端——而不是为每个终端做一个特定的版本.这个概念是为解决移动互联网 ...
- css zoom属性兼容ie,firefox,chrome
jquery代码: $("body").css({ "zoom":"2", "transform":"scal ...
- Linux基础知识整理
一.基础知识 1.Linux简介 Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户.多任务.支持多线程和多CPU的操作系统.它能运行主要的UNIX工具软件 ...
- Spring_的jar详细说明
org.springframework.aop ——Spring的面向切面编程,提供AOP(面向切面编程)的实现 org.springframework.asm——spring 2.5.6的时候需要a ...
- Ubuntu虚拟机中断后重启网络断接错误解决方案
因为该死的windows自动更新,所以vmplayer经常会被强制关闭. 但重新启动后,会发生不能连接到网络的情况显示: waiting for the network configuration…… ...
- PL/SQL Developer不配置TNS直接登录
如果只是临时登录,就没必要去配置一个TNS了,Database那里直接输入<IP>:<PORT>/<服务器SERVER_NAME> EBS的直接登录: http:/ ...
- mysql 优化
1.存储过程造数据 CREATE DEFINER=`root`@`localhost` PROCEDURE `generate_test_data`(`n` int) begin declare i ...
- 利用PHP绘图函数实现简单验证码功能
index.php __________________________________________________________________________________________ ...
- canvas的save与restore方法的作用
网上搜罗了一堆资料,最后总结一下. save:用来保存Canvas的状态.save之后,可以调用Canvas的平移.放缩.旋转.错切.裁剪等操作. restore:用来恢复Canvas之前保存的状态. ...
- HDU5934 强连通分量
题目:http://acm.hdu.edu.cn/showproblem.php?pid=5934 根据距离关系建边 对于强连通分量来说,只需引爆话费最小的炸弹即可引爆整个强连通分量 将所有的强连通分 ...