在开发过程中,我们希望控制台输出一些比较有价值的信息,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,迎娶白富美,走向人生巅峰(误)!