【Java EE 学习 77 上】【数据采集系统第九天】【经过AOP实现日志管理】【经过Spring石英调度动态生成日志表】【日志分表和查询】

1、需求分析

  日志数据在不少行业中都是很是敏感的数据,它们不能删除只能保存和查看,这样日志表就会愈来愈大,咱们不可能永远让它无限制的增加下去,必须采起一种手段将数据分散开来。假设如今整个数据库须要保存的数据量比较少,可是只有日志表的数据量会很大,在这种状况下咱们能够考虑使用分表策略分散保存日志数据。java

  针对当前系统来说,能够这么作:每月建立一张新表用于保存当月的日志数据。固然这只是初期的保存日志的思路。web

  1.解决问题的方法就是分表,那么何时建立新表呢?

  (1).若是服务器不关闭,假设一直处于运行状态,每月的月末建立下一个月的日志表好像是比较不错的,可是这和软件测试的边界值条件相符合,是很是容易出错的地方,因此,在每月的中间建立新表是比较不错的选择;处于系统的健壮性考虑,建立接下来两个月的表是比较合适的。spring

  (2).当前月的表如何建立。sql

   服务器不可能一开始就是开启的,因此咱们须要在服务器开启的时候就建立好当前月的日志表,可是只是建立当前月的日志表仍是不够的,还须要建立接下来两个月使用的日志表,或许你会问为何,以后就交给某个定时器每月中间自动建立表不就能够了吗?可是你没有考虑到,若是服务器的启动时间正好是在一个月的下半个月怎么办?这时候到了下个月的时候就没有日志表可用了。数据库

  2.接下来还有问题就是怎么将日志信息保存到当前月的日志表

  这个其实是比较简单的,咱们经过一个规则根据时间动态的指定建立的表名,一样在插入数据的时候也可以使用相同的规则插入到当前月的表中。tomcat

  3.怎么将数据从多个表中取出来

  使用sql的union链接查询便可达到目的。服务器

2、使用spring的石英调度定时建立日志表

  使用spring石英调度任务的步骤以下:app

  1.建立石英调度任务类

  该类必须继承org.springframework.scheduling.quartz.QuartzJobBean抽象类并重写executeInternal方法dom

 1 package com.kdyzm.schedual;
 2 
 3 import org.quartz.JobExecutionContext;
 4 import org.quartz.JobExecutionException;
 5 import org.springframework.scheduling.quartz.QuartzJobBean;
 6 
 7 import com.kdyzm.service.LogService;
 8 import com.kdyzm.utils.LogUtils;
 9 /**
10  * 建立的石英任务:使用spring集成的石英调度,动态生成日志表
11  * @author kdyzm
12  *
13  */
14 public class GenerateLogsTableTask extends QuartzJobBean{
15     private LogService logService;
16     public LogService getLogService() {
17         return logService;
18     }
19     public void setLogService(LogService logService) {
20         this.logService = logService;
21     }
22     /**
23      * 执行调度任务的方法
24      * 每个月15号建立下两个月须要用到的日志表
25      */
26     @Override
27     protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
28 String tableName=LogUtils.createGenerateLogsTableName(1); 29         String sql="create table if not exists "+tableName+" like logs";
30         this.logService.executeSql(sql);
31         System.out.println(tableName+" 表生成了!");
32         tableName=LogUtils.createGenerateLogsTableName(2);
33         sql="create table if not exists "+tableName+" like logs";
34         this.logService.executeSql(sql);
35         System.out.println(tableName+" 表生成了!");
36     }
37 }

    这里须要建立一个LogUtils工具类而且封装一个生成日志表名的方法:该方法的参数是一个偏移量,若是是整数表示下几个月,若是是负数表示是上几个月,使用Calendar类给出的方法可以很是快速的计算出来加上几个月或者减去几个月以后的日期。ide

 1 package com.kdyzm.utils;
 2 
 3 import java.util.Calendar;
 4 
 5 /**
 6  * 专门针对日志生成流程定义的工具类
 7  * @author kdyzm
 8  *
 9  */
10 public class LogUtils {
11     //动态生成日志表名的方法
12     public static String createGenerateLogsTableName(int offset){
13         Calendar calendar=Calendar.getInstance();
14 //        month=(month+offset-1)%month+1;
15         //计算偏移以后的动态表名
16         calendar.add(Calendar.MONTH, offset);
17         int year=calendar.get(Calendar.YEAR);
18         int month=calendar.get(Calendar.MONTH)+1;
19         return "logs_"+year+"_"+month;
20     }
21 }

  2.配置applicationConext.xml

  为了更加清晰的完成该项任务,单独使用一个配置文件完成该项任务的配置。

    配置步骤:

    (1)使用org.springframework.scheduling.quartz.JobDetailBean封装石英任务

1 <bean id="jobDetailBean" class="org.springframework.scheduling.quartz.JobDetailBean">
2     <property name="jobClass" value="com.kdyzm.schedual.GenerateLogsTableTask"></property>
3     <!-- 经过spring管理的bean必须经过这种方式注入到schema中 -->
4     <property name="jobDataAsMap">
5         <map>
6             <entry key="logService" value-ref="logService"></entry>
7         </map>
8     </property>
9 </bean>

    (2)设置触发器Bean,设置任务的调度策略

1 <!-- 触发器bean,设置任务的调度策略 -->
2 <bean id="cronTriggerBean" class="org.springframework.scheduling.quartz.CronTriggerBean">
3     <property name="jobDetail" ref="jobDetailBean"></property>
4     <property name="cronExpression">
5         <!-- 这个表达式的意思是:每月的15号 -->
6         <value>0 0 0 15 * ? *</value>
7     </property>
8 </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激活触发器,启动石英任务

1 <!-- 调度器工厂bean,激活触发器,启动石英任务的 -->
2 <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
3     <property name="triggers">
4         <ref bean="cronTriggerBean"/>
5     </property>
6 </bean>

    从上述三个步骤来看,后一个步骤依次对前面的任务进行了封装。

  完整的schedual.xml配置文件:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
 4     xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
 5     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
 6         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
 7         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
 8         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">
 9     <!-- 配置调度任务的spring配置文件 -->
10     
11     <!-- 任务明细bean,对石英任务进行封装 -->
12     <bean id="jobDetailBean" class="org.springframework.scheduling.quartz.JobDetailBean">
13         <property name="jobClass" value="com.kdyzm.schedual.GenerateLogsTableTask"></property>
14         <!-- 经过spring管理的bean必须经过这种方式注入到schema中 -->
15         <property name="jobDataAsMap">
16             <map>
17                 <entry key="logService" value-ref="logService"></entry>
18             </map>
19         </property>
20     </bean>
21     <!-- 触发器bean,设置任务的调度策略 -->
22     <bean id="cronTriggerBean" class="org.springframework.scheduling.quartz.CronTriggerBean">
23         <property name="jobDetail" ref="jobDetailBean"></property>
24         <property name="cronExpression">
25             <!-- 这个表达式的意思是:每月的15号 -->
26             <value>0 0 0 15 * ? *</value>
27         </property>
28     </bean>
29     <!-- 调度器工厂bean,激活触发器,启动石英任务的 -->
30     <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
31         <property name="triggers">
32             <ref bean="cronTriggerBean"/>
33         </property>
34     </bean>
35 </beans>
schedual.xml

  3.配置web.xml配置文件

    因为配置文件是单独的配置文件,因此须要在web.xml配置文件中单独声明:

1 <context-param>
2     <param-name>contextConfigLocation</param-name>
3     <param-value>classpath:spring/applicationContext.xml,classpath:spring/schedual.xml</param-value>
4 </context-param>

3、使用Spring监听器动态建立当前月和接下来两个月须要用到的日志表。

  这和初始化权限表的的流程几乎彻底相同,只是不须要配置applicationContext.xml配置文件了,只须要给监听器类加上注解归入spring管理便可。 

 1 package com.kdyzm.listener;
 2 
 3 import javax.annotation.Resource;
 4 
 5 import org.springframework.context.ApplicationEvent;
 6 import org.springframework.context.ApplicationListener;
 7 import org.springframework.context.event.ContextRefreshedEvent;
 8 import org.springframework.stereotype.Component;
 9 
10 import com.kdyzm.service.LogService;
11 import com.kdyzm.utils.LogUtils;
12 /**
13  * 动态生成当前月份的日志表
14  * 这里一次性生成三个月份的日志表,防止服务器启动的时间是在一个月的下半个月,即在15号以后
15  * @author kdyzm
16  *
17  */
18 @Component
19 public class InitLogTableListener implements ApplicationListener{
20     @Resource(name="logService")
21     private LogService logService;
22     @Override
23     public void onApplicationEvent(ApplicationEvent event) {
24         if(event instanceof ContextRefreshedEvent){
25             //生成当前月的日志表
26             String tableName=LogUtils.createGenerateLogsTableName(0); 27             String sql="create table if not exists "+tableName+" like logs";
28             logService.executeSql(sql);
29             System.out.println(tableName+" 表已经生成!");
30             
31             //生成下一个月的日志表
32             tableName=LogUtils.createGenerateLogsTableName(1); 33             sql="create table if not exists "+tableName+" like logs";
34             logService.executeSql(sql);
35             System.out.println(tableName+" 表已经生成!");
36             
37             //生成第二个月的日志表
38             tableName=LogUtils.createGenerateLogsTableName(2);
39             sql="create table if not exists "+tableName+" like logs";
40             logService.executeSql(sql);
41             System.out.println(tableName+" 表已经生成!");
42         }
43     }
44 }

    这时候Service中必须提供执行SQL语句的方法,使用hibernate中的SQLQuery对象便可完成该项任务,略。

完成2、三中的任务以后,动态建立当前月份的日志表的任务和定时建立后两个月的日志表的功能就已经实现了:启动tomcat服务器的时候就可以发现建立三张表的日志信息,同时每月的14号00:00就会发现建立两个表的日志信息。

  

4、保存日志到日志分表

  以前保存日志的时候保存到的表是log表,如今的任务是须要将日志保存到当前月对应的表中。这就须要考虑从哪里实现该该方法比较好,可以改动最小的代码实现该功能。

  回顾Service,Service调用saveEntity方法保存日志对象,实际上调用的方法是LogDaoImpl对象中的方法,因为该类中没有定义saveEntity方法,因此会自动到父类中查找是否有该方法,结果在BaseDaoImpl类中找到了该方法,因此就调用了该方法,采用的泛型是Log,因此保存到了log表中。基于该流程,最合适的地方只有两个,一个是Service中国的save方法,另外一个就是DAO中的save方法,综合考虑仍是调用DAO中的方法,在LogDaoImpl类中重写BaseDaoImpl中的saveEntity方法,这样Service中的代码根本不须要改变就可以实现预约的目标了。

 1 package com.kdyzm.dao.impl;
 2 
 3 import java.util.Collection;
 4 
 5 import org.springframework.stereotype.Repository;
 6 
 7 import com.kdyzm.dao.base.impl.BaseDaoImpl;
 8 import com.kdyzm.domain.Log;
 9 import com.kdyzm.utils.LogUtils;
10 import com.kdyzm.utils.StringUtils;
11 @Repository("logDao")
12 public class LogDaoImpl extends BaseDaoImpl<Log>{
13     /**
14      * 重写父类BaseDaoImpl中的方法,这里要动态指定表名,因此不能再使用hibernate提供的保存数据的方法
15      * 直接使用原生的slq语句来保存便可
16      */
17  @Override 18     public void saveEntity(Log log) { 19         String tableName=LogUtils.createGenerateLogsTableName(0);
20         String sql="insert into "+tableName+" ("
21                 + " logId,operateParams,operateResult,operator,operatorDate,operatorName,resultMessage) "
22                 + "values (?,?,?,?,?,?,?)";
23         this.executeSql(sql,
24                 StringUtils.getUUIDString(),
25                 log.getOperateParams(),
26                 log.getOperateResult(),
27                 log.getOperator(),
28                 log.getOperatorDate(),
29                 log.getOperatorName(),
30                 log.getResultMessage()
31                 );
32     }
33 }

5、读取日志

  因为日志已经分散到了多个表中,若是想要获取指定的日志数据,就必须到多个表中查询日志数据,这样就须要使用到了union关键字;和以前的保存日志的遇到的问题相同,以前查询全部日志使用的表是log表,如今须要查询当前月份对应的表,处理方式和以前保存日志数据使用的方式相同,只须要重写LogDaoImpl中的findAllEntities方法便可,默认查询当前月和上一个月的日志表(其实本应该指定范围的,暂时化简一下处理过程,只是查询当前月份的日志数据和上一个月份的日志数据),这样最终LogDaoImpl的形态就变成了这样:

 1 package com.kdyzm.dao.impl;
 2 
 3 import java.util.Collection;
 4 
 5 import org.springframework.stereotype.Repository;
 6 
 7 import com.kdyzm.dao.base.impl.BaseDaoImpl;
 8 import com.kdyzm.domain.Log;
 9 import com.kdyzm.utils.LogUtils;
10 import com.kdyzm.utils.StringUtils;
11 @Repository("logDao")
12 public class LogDaoImpl extends BaseDaoImpl<Log>{
13     /**
14      * 重写父类中的方法,这里要动态指定表名,因此不能再使用hibernate提供的保存数据的方法
15      * 直接使用原生的slq语句来保存便可
16      */
17     @Override
18     public void saveEntity(Log log) {
19         String tableName=LogUtils.createGenerateLogsTableName(0);
20         String sql="insert into "+tableName+" ("
21                 + " logId,operateParams,operateResult,operator,operatorDate,operatorName,resultMessage) "
22                 + "values (?,?,?,?,?,?,?)";
23         this.executeSql(sql,
24                 StringUtils.getUUIDString(),
25                 log.getOperateParams(),
26                 log.getOperateResult(),
27                 log.getOperator(),
28                 log.getOperatorDate(),
29                 log.getOperatorName(),
30                 log.getResultMessage()
31                 );
32     }
33     //重写该方法,由于该方法必须实现多表联合查询
34  @Override 35     public Collection<Log> findAllEntities() { 36         String tableName=LogUtils.createGenerateLogsTableName(0);//当前月的日志表
37         String talbeName1=LogUtils.createGenerateLogsTableName(-1);//上个月的日志表
38         String sql="select * from "+tableName+" union select * from "+talbeName1+" order by operatorDate desc";
39         return this.findAllEntitiesBySql(sql);
40     }
41 }

 

最终的效果和以前未作分表处理的效果彻底相同。

相关文章
相关标签/搜索