在开发过程中,我们希望控制台输出一些比较有价值的信息,JFinal开启调试模式以后,可以显示如下信息,这是极好的。
JFinal action report -------- 2017-06-19 19:58:05 ------------------------------ Url : GET /sys/terminal/getListData Controller : cn.ablefly.controller.sys.TerminalController.(TerminalController.java:1) Method : getListData Interceptor : cn.ablefly.core.auth.interceptor.SysLogInterceptor.(SysLogInterceptor.java:1) cn.ablefly.core.auth.interceptor.AuthorityInterceptor.(AuthorityInterceptor.java:1) Parameter : sord=asc page=1 nd=1497873485305 sidx= rows=10 _search=false
可惜的是,在显示sql语句方面,自带的调试输出效果就比较一般了,只会简单的显示出输出的sql语句,占位符也是用?号代替的。需要开启下面的注释掉的第二行代码
ActiveRecordPlugin arp = new ActiveRecordPlugin(druidPlugin); //arp.setShowSql(true); //arp.setDevMode(true);
显示效果如下:
Sql: select * from `evnet_terminal` where `id` = ?
显然,这种简陋的显示对我们调试复杂的sql语句和排错是没有太多价值的。那么我们就来动手改造一下吧!改造之后,我们的显示效果应该是这个样子的!完整的显示了执行的sql,占位符也被实际参数更换,更显示出了sql执行所花费的时间。
20:08:45.903 [qtp1803817267-27] DEBUG jdbc.sqltiming -  com.alibaba.druid.filter.FilterChainImpl.preparedStatement_executeQuery(FilterChainImpl.java:2714)
10. select * from `evnet_terminal` where `id` = 14 
 {executed in 0 msec}1、确定所需要的jar包依赖。
不同项目所用的日志框架可能有所不同,所以项目中和日志有关的jar包也不尽相同,这里我只列出我发现的两种情况。
首先,必须引入的jar包包括如下三个:
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.16</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.7</version> </dependency> <dependency> <groupId>com.googlecode.log4jdbc</groupId> <artifactId>log4jdbc</artifactId> <version>1.2</version> </dependency>
其中真正帮助我们显示完整sql数据的jar是log4jdbc-1.2.jar,该框架的详细介绍如下:
https://github.com/arthurblake/log4jdbc/blob/wiki/ProjectHome.md
还有一个jar包依赖,需要2选1,如果你的项目中已经引入了其中任何一个,就不需要再引入另一个了。2选1的jar包如下:
<dependency> <groupId>org.slf4j</groupId> <artifactId>log4j-over-slf4j</artifactId> <version>1.7.7</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.7</version> </dependency>
jar包的版本不是必须使用1.7.7的,其他版本也可以尝试。同时引入以上两个包会产生堆栈溢出问题,详情可参考这篇文档:
http://blog.csdn.net/kxcfzyk/article/details/38613861
2、修改数据库配置信息中的jdbcUrl配置和driverClass配置,示例如下:
jdbcUrl = jdbc:log4jdbc:mysql://localhost:3306/evnet?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull driverClass=net.sf.log4jdbc.DriverSpy
注意,jdbcUrl中的"jdbc:"与":mysql"之间的"log4jdbc"就是我们对url的改变之处,其他的数据库描述符的配置,也是一样的思路,在适当的位置加上"log4jdbc"即可。而driverClass的配置是固定的。
3、修改Jfinal核心配置类,用包含四个参数的构造函数配置数据库连接池。
在大部分情况下,我们在项目的核心配置类中,配置连接池,不管是c3p0还是druid或者使用其他的连接池,我们都会用三个参数的构造方法,如下示例为建立一个druid连接池的封装方法:
public static DruidPlugin createDruidPlugin() {
   DruidPlugin dp = new DruidPlugin(PropKit.get("jdbcUrl"), PropKit.get("user"), PropKit.get("password"));
   dp.addFilter(new StatFilter());
   dp.addFilter(new Slf4jLogFilter());
   WallFilter wall = new WallFilter();
   wall.setDbType("mysql");
   dp.addFilter(wall);
   return dp;
}我们只需要将这一行关键代码
DruidPlugin dp = new DruidPlugin(PropKit.get("jdbcUrl"), PropKit.get("user"), PropKit.get("password"));后面多加一个参数,即可指定所使用的driverClass,即代码改成
DruidPlugin dp = new DruidPlugin(PropKit.get("jdbcUrl"), PropKit.get("user"), PropKit.get("password"),PropKit.get("driverClass"));4、修改日志输出配置文件
这里也分为两种情况,比较老的项目可能使用log4j.properties做配置文件比较多,现在的大部分项目都是用logback,也就是使用logback.xml做配置文件。两种情况下配置文件的主要配置如下:
1)使用log4j.properties:
log4j.rootLogger=WARN, stdout, file
log4j.rootLogger=ERROR, stdout, file
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%n%-d{yyyy-MM-dd HH:mm:ss}%n[%p]-[Thread: %t]-[%C.%M()]: %m%n
# Output to the File
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.file.File=./jfinal_demo.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%n%-d{yyyy-MM-dd HH:mm:ss}%n[%p]-[Thread: %t]-[%C.%M()]: %m%n
log4j.logger.jdbc.sqltiming=INFO,console  
#log4j.logger.jdbc.sqlonly=DEBUG,console
#log4j.logger.jdbc.connection=INFO,console
#log4j.appender.console=org.apache.log4j.ConsoleAppender 
#log4j.appender.console.layout=org.apache.log4j.PatternLayout
#log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %m%n%n以上配置来自于jfinal-demo工程,其实我们只需要注意新增的两行配置
log4j.logger.jdbc.sqltiming=INFO,console #log4j.logger.jdbc.sqlonly=DEBUG,console
第一行即表示输出sql的同时,也输出sql的执行所耗费时间,第二行只会输出执行的sql,不显示执行耗费时间。
2)使用logback.xml:
因为对logback的配置方式并不熟悉,所以目前我为了实现控制台实现完整sql,过滤掉其他次要日志信息的配置方式也许不是最优的。仅供各位参考,这是我项目本身的日志配置:
logback.xml的内容:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   <property name="ABSOLUTE_PATH"
      value="/logs/evnet-web" />
   <include resource="logback-application.xml" />
   <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
      <encoder>
         <pattern>
            %date{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n
         </pattern>
      </encoder>
   </appender>
   <appender name="launchAppender"
      class="ch.qos.logback.core.rolling.RollingFileAppender">
      <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
         <fileNamePattern>
            ${ABSOLUTE_PATH}/%d{yyyy-MM-dd,aux}/launch-%d{yyyy-MM-dd-HH}.log
         </fileNamePattern>
         <maxHistory>1000</maxHistory>
      </rollingPolicy>
      <encoder>
         <pattern>
            %date{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n
         </pattern>
      </encoder>
   </appender>
   <appender name="applicationAppender"
      class="ch.qos.logback.core.rolling.RollingFileAppender">
      <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
         <fileNamePattern>
            ${ABSOLUTE_PATH}/%d{yyyy-MM-dd,aux}/application-%d{yyyy-MM-dd-HH}.log
         </fileNamePattern>
         <maxHistory>1000</maxHistory>
      </rollingPolicy>
      <encoder>
         <pattern>
            %date{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n
         </pattern>
      </encoder>
   </appender>
   <appender name="sysapiAppender"
           class="ch.qos.logback.core.rolling.RollingFileAppender">
      <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
         <fileNamePattern>
            ${ABSOLUTE_PATH}/%d{yyyy-MM-dd,aux}/sysapi-%d{yyyy-MM-dd-HH}.log
         </fileNamePattern>
         <maxHistory>1000</maxHistory>
      </rollingPolicy>
      <encoder>
         <pattern>
            %date{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n
         </pattern>
      </encoder>
   </appender>
   <appender name="terminalAppender"
           class="ch.qos.logback.core.rolling.RollingFileAppender">
      <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
         <fileNamePattern>
            ${ABSOLUTE_PATH}/%d{yyyy-MM-dd,aux}/terminal-%d{yyyy-MM-dd-HH}.log
         </fileNamePattern>
         <maxHistory>1000</maxHistory>
      </rollingPolicy>
      <encoder>
         <pattern>
            %date{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n
         </pattern>
      </encoder>
   </appender>
   <logger name="cn.ablefly" level="DEBUG" additivity="false">
      <appender-ref ref="applicationAppender" />
   </logger>
   <logger name="cn.ablefly.evnet" level="DEBUG" additivity="false">
      <appender-ref ref="terminalAppender" />
   </logger>
   <logger name="cn.ablefly.controller.sysapi" level="DEBUG" additivity="false">
      <appender-ref ref="sysapiAppender" />
   </logger>
   <root level="DEBUG">
      <appender-ref ref="console" />
   </root>
   
   <logger  level="debug" additivity="false">
   </logger>
</configuration>关键的代码是最后面几行:
<root level="DEBUG"> <appender-ref ref="console" /> </root> <logger level="debug" additivity="false"> </logger>
如果不加任何级别过滤,控制台的输出信息将十分巨大。所以在配置文件头部引入的logback-application.xml文件中,加入了如下日志级别过滤配置:
logback-application.xml的内容:
<?xml version="1.0" encoding="UTF-8"?> <included> <logger name="jdbc.sqltiming" level="DEBUG" /> <logger name="jdbc" level="WARN" /> <logger name="org.eclipse" level="WARN" /> <logger name="druid" level="WARN" /> <logger name="io.netty" level="WARN" /> <logger name="net.sf.ehcache" level="WARN" /> </included>
关键是前两个logger,让log4jdbc中除了sqltiming之外的所有logger全部只显示warn级别以上的日志。其他的过滤可以根据自身项目的需要配置。但是我觉得这个不是最优解。因为时间关系,没有深入去研究logback的配置原理和技巧。如果大家有更优雅的配置方式,希望不灵赐教。
好了,完成了以上几部以后,我们的目的就已经实现了,注意,这个时候我们就不需要在jfinal的主配置类中再开启ActiveRecord插件的showSql功能了!最后看下jfinal调试模式开启下的搭配使用效果:
21:10:48.440 [qtp1632409556-33] DEBUG jdbc.sqltiming -  com.alibaba.druid.filter.FilterChainImpl.preparedStatement_executeQuery(FilterChainImpl.java:2714)
9. select * from evnet_terminal limit 0, 10 
 {executed in 1 msec}
21:10:48.451 [qtp1632409556-33] DEBUG jdbc.sqltiming -  com.alibaba.druid.filter.FilterChainImpl.preparedStatement_executeUpdate(FilterChainImpl.java:2723)
9. insert into `sys_log`(`uid`, `err_msg`, `err_code`, `method_name`, `start_time`, `spend_time`, 
`from`, `class_name`, `url`, `ip`) values(1, '', 0, 'getListData', '2017-06-19 21:10:48', 14, 
'http://localhost:8080/sys/rate', 'cn.ablefly.controller.sys.RateController', '/sys/rate/getListData', 
'0:0:0:0:0:0:0:1') 
 {executed in 1 msec}
JFinal action report -------- 2017-06-19 21:10:48 ------------------------------
Url         : GET /sys/rate/getListData
Controller  : cn.ablefly.controller.sys.RateController.(RateController.java:1)
Method      : getListData
Interceptor : cn.ablefly.core.auth.interceptor.SysLogInterceptor.(SysLogInterceptor.java:1)
              cn.ablefly.core.auth.interceptor.AuthorityInterceptor.(AuthorityInterceptor.java:1)
Parameter   : sord=asc  page=1  nd=1497877848420  sidx=  rows=10  _search=false  
--------------------------------------------------------------------------------完美配合!
最后给大家一个彩蛋吧,也是从别的地方搬运过来的,可以作为数据库方法测试的基类使用。
JFinalModelCase.java类
package cn.ablefly.test.evnet.sysapi;
/**
 * Created by xyzx1 on 2017-05-18.
 */
import cn.ablefly.model.EvnetTerminal;
import cn.ablefly.model._MappingKit;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.wall.WallFilter;
import com.jfinal.kit.PropKit;
import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
import com.jfinal.plugin.activerecord.dialect.MysqlDialect;
import com.jfinal.plugin.druid.DruidPlugin;
import org.junit.After;
import org.junit.BeforeClass;
/**
 * @author ilgqh
 * JFinal的Model测试用例
 *
 */
public class JFinalModelCase{
    protected static DruidPlugin dp;
    protected static ActiveRecordPlugin activeRecord;
    /**
     * 数据连接地址
     */
    private static String  URL = "jdbc:mysql://localhost:3306/evnet?useUnicode=true&characterEncoding=UTF-8";
    //private static final String  URL = "jdbc:mysql://10.10.120.204:3306/evnet?useUnicode=true&characterEncoding=UTF-8";
    /**
     * 数据库账号
     */
    private static String  USERNAME = "root";
    /**
     * 数据库密码
     */
    private static  String  PASSWORD = "root";
    /**
     * 数据库驱动
     */
    private static final String DRIVER = "com.mysql.jdbc.Driver";
    /**
     * 数据库类型(如mysql,oracle)
     */
    private static final String DATABASE_TYPE = "mysql";
    /**
     * @throws java.lang.Exception
     */
    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
        PropKit.use("a_little_config.txt");
        URL=PropKit.get("jdbcUrl");
        USERNAME=PropKit.get("user");
        PASSWORD=PropKit.get("password");
        dp=new DruidPlugin(URL, USERNAME,PASSWORD,DRIVER);
        dp.addFilter(new StatFilter());
        dp.setInitialSize(3);
        dp.setMinIdle(2);
        dp.setMaxActive(5);
        dp.setMaxWait(60000);
        dp.setTimeBetweenEvictionRunsMillis(120000);
        dp.setMinEvictableIdleTimeMillis(120000);
        WallFilter wall = new WallFilter();
        wall.setDbType(DATABASE_TYPE);
        dp.addFilter(wall);
        dp.getDataSource();
        dp.start();
        activeRecord = new ActiveRecordPlugin(dp);
        activeRecord.setDialect(new MysqlDialect())
        //.setDevMode(true)
        //.setShowSql(true)  //是否打印sql语句
        ;
        //映射数据库的表和继承与model的实体
        //只有做完该映射后,才能进行junit测试
        _MappingKit.mapping(activeRecord);
        activeRecord.start();
    }
    /**
     * @throws java.lang.Exception
     */
    @After
    public void tearDown() throws Exception {
        activeRecord.stop();
        dp.stop();
    }
}当你需要为数据库的CURD方法做测试时,只要继承一下这个类,如写一个DBTest.java
package cn.ablefly.test.evnet.sysapi;
import cn.ablefly.model.DictData;
import cn.ablefly.model.EvnetTerminal;
import org.junit.Test;
/**
 * Created by xyzx1 on 2017-05-18.
 */
public class DBTest extends JFinalModelCase {
    @Test    public void testQueryTerminal(){
        EvnetTerminal terminal= EvnetTerminal.dao.findTerminalByTerminalId("075586511588001");
        System.out.println(terminal.toJson());
}
    @Test    public void testQueryDictName(){
        String name=DictData.dao.queryDataName(1,101003);
        System.out.println(name);
    }
}然后在这个类里面写上任意方法调用你想测试的方法即可。加上@Test注解,即可即时运行,不需要启动web应用。
最后感谢在解决上述问题中波总给予的指导!希望在大家的努力下,JFinal能越来越强大,成为我们手中的神兵利器~~解决各种开发中的问题~~从而升职CEO,迎娶白富美,走向人生巅峰(误)!
 
 
 
 
 
 
 
 
 
 
 
 
