【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 }

 

最終的效果和以前未作分表處理的效果徹底相同。

相關文章
相關標籤/搜索