上篇文章我们总结了权限实施和优化的一些方案,也引出降低数据库压力的一些方法,今天我们就具体说下如何降低数据库的存储,访问,负载压力。
谈这些之前,我们说下日志功能,对于我之前的项目,我一直在使用log4j或者logback也实现记录功能,效果上因为记录范围小,数据量小也就体现不出来其差距,总之就是实现了日志功能,这次在学习了数据采集系统之后,我学到了更全面的日志方案。我们说日志功能是系统级的,是对整个系统业务的操作记录,一般我们只需记录数据的添加,修改,删除情况,对查询不再记录,当然也有安全级别比较高的要记录这些,那既然是整个业务的把控,我们应该想到面向切面的Aop,其环绕通知,AOP详细介绍:http://jinnianshilongnian.iteye.com/blog/1474325 。就是对所有业务横向控制,典型案例就是我们对事务的管理,这两者方法是一样的,也就是像配置事务环绕通知那样,做个日志切面,然后写出各层功能。
首先就是数据库创建表,定义pojo对象,将记录信息存入表中,这里我把日志切面Spring配置方法写下来:
<!-- logger -->
<bean id="logger" class="cn.itcast.surveypark.advice.Logger">
<property name="logService" ref="logService" />
</bean>
<!-- aop事务配置 -->
<aop:config>
<!-- 事务切入点 -->
<aop:pointcut expression="execution(* *..*Service.*(..))" id="txPointcut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
<!-- 配置日志切面 -->
<aop:aspect id="loggerAspect" ref="logger">
<aop:around method="record" pointcut-ref="txPointcut"/>
</aop:aspect>
</aop:config>实战后我们发现有一点特别要注意,就是日志记录的死循环bug,我们想,日志也是一个业务,当你操作任何一个对象,日志表也要添加一条记录,你的添加业务又会让日志来记录,那么不就陷入一个死循环,事实确实是这样,所以我们要指定日志切面所要expression的范围,然后把logService给分离出来你就行了:
<!-- aop事务配置 -->
<aop:config>
<!-- 事务切入点 -->
<aop:pointcut expression="execution(* *..*Service.*(..))" id="txPointcut"/>
<!-- 日志切入点 -->
<aop:pointcut expression="(execution(* *..*Service.save*(..))
or execution(* *..*Service.update*(..))
or execution(* *..*Service.delete*(..))
or execution(* *..*Service.batch*(..))
or execution(* *..*Service.new*(..))) and !bean(logService)"
id="loggerPointcut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
<!-- 配置日志切面 -->
<aop:aspect id="loggerAspect" ref="logger">
<aop:around method="record" pointcut-ref="loggerPointcut"/>
</aop:aspect>
</aop:config>使用调度器动态生成日志表。具体做法就是我们配置定时器,每月生成一个新的日志表来存储日志信息,这样就不用担心单表查询量太大问题了。具体做法如下:
dao和service增加执行原生sql语句的功能.
dao
-----------------
public interface BaseDao<T> {
...
public void executeSQL(String sql,Object...objects);
}
public class BaseDaImpl<T> ...{
...
//执行原生的sql语句
public void executeSQL(String sql,Object...objects){
SQLQuery q = sf.getCurrentSession().createSQLQuery(sql);
for(int i = 0 ; i < objects.length ; i ++){
q.setParameter(i, objects[i]);
}
q.executeUpdate();
}
}
service
-----------------------
public interface BaseService<T> {
...
public void executeSQL(String sql,Object...objects);
}
public abstract class BaseServiceImpl<T> implements BaseService<T> {
...
//执行原生的sql语句
public void executeSQL(String sql,Object...objects){
dao.executeSQL(sql, objects);
}
} 创建sql写好了,如何让系统自己定时执行sql,创建日志表就是新的问题,这里两个方案:一个是定时器调度,就是周期固定,每隔一段时间执行一次;第二个就是石英调度,好处就是时间可以不固定,可以指定每月的几号进行操作。这里我们使用后者方法,配置石英调度:
因为是Spring框架,我们引入使用spring集成的石英调度,动态生成日志表,首先引入quartz类库包com.springsource.org.quartz-1.6.2.jar,它还有自己的配置文件可以和Spring.xml写在一块,也可以单独拿出来,我配置了调度任务xml
[conf/schedules.xml]
<?xml version="1.0"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
<!-- 任务明细bean,对石英任务包装 -->
<bean id="jobDetailBean" class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass" value="cn.itcast.surveypark.schedule.GenerateLogsTableTask" />
<property name="jobDataAsMap">
<map>
<entry key="logService" value-ref="logService" />
</map>
</property>
</bean>
<!-- cron触发器bean,设置任务的调度策略的 -->
<bean id="cronTriggerBean" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail" ref="jobDetailBean" />
<!-- cron表达式 -->
<property name="cronExpression">
<value>0 0 0 15 * ?</value>//设置执行的时间,每月的15号,秒,分,时,日,月,年;任意月用*,年用?,假如每天早上6点就是0 0 6 * * ?</property>
</bean>
<!-- 调度器工厂bean,激活触发器,启动石英任务的 -->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<ref bean="cronTriggerBean"/>
</property>
</bean>
</beans>然后配置web.xml文件,一同加载spring的所有配置文件:
<!-- 通过上下文参数指定spring配置文件的位置 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:beans.xml,classpath:schedules.xml</param-value>
</context-param>这样基本的石英调度就完成了,现在又要考虑系统刚开始时的日志表问题,我们总不能配置一次就自己建各表,移动性太差,这时我们就可以之前学的初始化时执行建表方法,先把系统启动时未来三个月的日志建出来,这样就没有后顾之忧,符合可扩展性的特点(scalable)。
顺便提一下,分表的查询,因为结构相同我们可以通过数据库关键字union来合并表数据:
/**
* 查询最近的日志信息,默认是2个月
*/
public List<Log> findNearestLogs(){
String sql = "select * from " + LogUtil.generateLogTableName(-1)
+ " union "
+ " select * from " + LogUtil.generateLogTableName(0);
return this.findObjectBySQL(sql) ;
}