痛点:默认的mybatis打印的SQL语句格式不带参数,使用占位符代替,有==》等多余字符,不可定制。影响阅读(个人觉得)。
解决:打印的mybatis的SQL语句根个人在图形化工具中写的一样。可自定义输出的格式。
先介绍一下我的环境,传统的SSM项目、日志打印使用的logback。
格式化mybatis打印SQL语句步骤:
1、导入jar包 通过maven的形式添加依赖
p6spy
p6spy
3.7.0
2、编写配置p6spy的配置文件:spy.properties
logMessageFormat:指向的是自己定义的日志格式类,下面有我的一个使用案例,可以参考使用
其他配置使用默认就好,具体配置的含义文末附上,或可以参考给出的参考文章
reloadproperties=true
appender=com.p6spy.engine.spy.appender.Slf4JLogger
#P6SpyLogger \u7C7B\u5168\u8DEF\u5F84\u540D
logMessageFormat=com.ciitc.utils.P6SpyLogger
databaseDialectDateFormat=yyyy-MM-dd hh:mm:ss
excludecategories=info,debug,result,resultset
3、在配置数据源的配置文件中添加:
bean:数据源对象的id
id:其他需要使用数据源时使用此ID 感觉就是p6spy将数据源进行了包装
4、创建属于自己的日志格式类:可参考使用
此类就是填补第二点需要自定义日志格式的类的坑
MySQLFormatter:自定义的格式SQL语句的类,其实是借鉴另一篇文章从hibernate中抽取出来的类(hibernate用来格式化日志的类),随后附上
package com.ciitc.utils;
import com.p6spy.engine.spy.appender.MessageFormattingStrategy;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* p6spy打印日志输出格式修改 1.只打印最终执行的sql. 2.sql换到下一行 3.结尾处增加分号,以标示sql结尾 Created by
* odelia on 2016/1/4.
*/
public class P6SpyLogger implements MessageFormattingStrategy {
private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared,
String sql) {
if (!sql.trim().equals("")) {
return format.format(new Date()) + " | took " + elapsed + "ms | " + category + " | connection "
+ connectionId + new MySQLFormatter().format(sql) + ";";
}
return "";
}
}
5、MySQLFormatter:格式化SQL的类
package com.ciitc.utils;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import java.util.StringTokenizer;
/**
*
* sql格式化工具(从hibernate中弄出来的)
*/
public class MySQLFormatter {
private static final Set BEGIN_CLAUSES = new HashSet();
private static final Set END_CLAUSES = new HashSet();
private static final Set LOGICAL = new HashSet();
private static final Set QUANTIFIERS = new HashSet();
private static final Set DML = new HashSet();
private static final Set MISC = new HashSet();
public static final String WHITESPACE = " \n\r\f\t";
static {
BEGIN_CLAUSES.add( "left" );
BEGIN_CLAUSES.add( "right" );
BEGIN_CLAUSES.add( "inner" );
BEGIN_CLAUSES.add( "outer" );
BEGIN_CLAUSES.add( "group" );
BEGIN_CLAUSES.add( "order" );
END_CLAUSES.add( "where" );
END_CLAUSES.add( "set" );
END_CLAUSES.add( "having" );
END_CLAUSES.add( "join" );
END_CLAUSES.add( "from" );
END_CLAUSES.add( "by" );
END_CLAUSES.add( "join" );
END_CLAUSES.add( "into" );
END_CLAUSES.add( "union" );
LOGICAL.add( "and" );
LOGICAL.add( "or" );
LOGICAL.add( "when" );
LOGICAL.add( "else" );
LOGICAL.add( "end" );
QUANTIFIERS.add( "in" );
QUANTIFIERS.add( "all" );
QUANTIFIERS.add( "exists" );
QUANTIFIERS.add( "some" );
QUANTIFIERS.add( "any" );
DML.add( "insert" );
DML.add( "update" );
DML.add( "delete" );
MISC.add( "select" );
MISC.add( "on" );
}
static final String indentString = " ";
static final String initial = "\n ";
public String format(String source) {
return new FormatProcess( source ).perform();
}
private static class FormatProcess {
boolean beginLine = true;
boolean afterBeginBeforeEnd = false;
boolean afterByOrSetOrFromOrSelect = false;
boolean afterValues = false;
boolean afterOn = false;
boolean afterBetween = false;
boolean afterInsert = false;
int inFunction = 0;
int parensSinceSelect = 0;
private LinkedList parenCounts = new LinkedList();
private LinkedList afterByOrFromOrSelects = new LinkedList();
int indent = 1;
StringBuilder result = new StringBuilder();
StringTokenizer tokens;
String lastToken;
String token;
String lcToken;
public FormatProcess(String sql) {
tokens = new StringTokenizer(
sql,
"()+*/-=<>'`\"[]," + WHITESPACE,
true
);
}
public String perform() {
result.append( initial );
while ( tokens.hasMoreTokens() ) {
token = tokens.nextToken();
lcToken = token.toLowerCase();
if ( "'".equals( token ) ) {
String t;
do {
t = tokens.nextToken();
token += t;
}
while ( !"'".equals( t ) && tokens.hasMoreTokens() ); // cannot handle single quotes
}
else if ( "\"".equals( token ) ) {
String t;
do {
t = tokens.nextToken();
token += t;
}
while ( !"\"".equals( t ) );
}
if ( afterByOrSetOrFromOrSelect && ",".equals( token ) ) {
commaAfterByOrFromOrSelect();
}
else if ( afterOn && ",".equals( token ) ) {
commaAfterOn();
}
else if ( "(".equals( token ) ) {
openParen();
}
else if ( ")".equals( token ) ) {
closeParen();
}
else if ( BEGIN_CLAUSES.contains( lcToken ) ) {
beginNewClause();
}
else if ( END_CLAUSES.contains( lcToken ) ) {
endNewClause();
}
else if ( "select".equals( lcToken ) ) {
select();
}
else if ( DML.contains( lcToken ) ) {
updateOrInsertOrDelete();
}
else if ( "values".equals( lcToken ) ) {
values();
}
else if ( "on".equals( lcToken ) ) {
on();
}
else if ( afterBetween && lcToken.equals( "and" ) ) {
misc();
afterBetween = false;
}
else if ( LOGICAL.contains( lcToken ) ) {
logical();
}
else if ( isWhitespace( token ) ) {
white();
}
else {
misc();
}
if ( !isWhitespace( token ) ) {
lastToken = lcToken;
}
}
return result.toString();
}
private void commaAfterOn() {
out();
indent--;
newline();
afterOn = false;
afterByOrSetOrFromOrSelect = true;
}
private void commaAfterByOrFromOrSelect() {
out();
newline();
}
private void logical() {
if ( "end".equals( lcToken ) ) {
indent--;
}
newline();
out();
beginLine = false;
}
private void on() {
indent++;
afterOn = true;
newline();
out();
beginLine = false;
}
private void misc() {
out();
if ( "between".equals( lcToken ) ) {
afterBetween = true;
}
if ( afterInsert ) {
newline();
afterInsert = false;
}
else {
beginLine = false;
if ( "case".equals( lcToken ) ) {
indent++;
}
}
}
private void white() {
if ( !beginLine ) {
result.append( " " );
}
}
private void updateOrInsertOrDelete() {
out();
indent++;
beginLine = false;
if ( "update".equals( lcToken ) ) {
newline();
}
if ( "insert".equals( lcToken ) ) {
afterInsert = true;
}
}
@SuppressWarnings( {"UnnecessaryBoxing"})
private void select() {
out();
indent++;
newline();
parenCounts.addLast( Integer.valueOf( parensSinceSelect ) );
afterByOrFromOrSelects.addLast( Boolean.valueOf( afterByOrSetOrFromOrSelect ) );
parensSinceSelect = 0;
afterByOrSetOrFromOrSelect = true;
}
private void out() {
result.append( token );
}
private void endNewClause() {
if ( !afterBeginBeforeEnd ) {
indent--;
if ( afterOn ) {
indent--;
afterOn = false;
}
newline();
}
out();
if ( !"union".equals( lcToken ) ) {
indent++;
}
newline();
afterBeginBeforeEnd = false;
afterByOrSetOrFromOrSelect = "by".equals( lcToken )
|| "set".equals( lcToken )
|| "from".equals( lcToken );
}
private void beginNewClause() {
if ( !afterBeginBeforeEnd ) {
if ( afterOn ) {
indent--;
afterOn = false;
}
indent--;
newline();
}
out();
beginLine = false;
afterBeginBeforeEnd = true;
}
private void values() {
indent--;
newline();
out();
indent++;
newline();
afterValues = true;
}
@SuppressWarnings( {"UnnecessaryUnboxing"})
private void closeParen() {
parensSinceSelect--;
if ( parensSinceSelect < 0 ) {
indent--;
parensSinceSelect = parenCounts.removeLast().intValue();
afterByOrSetOrFromOrSelect = afterByOrFromOrSelects.removeLast().booleanValue();
}
if ( inFunction > 0 ) {
inFunction--;
out();
}
else {
if ( !afterByOrSetOrFromOrSelect ) {
indent--;
newline();
}
out();
}
beginLine = false;
}
private void openParen() {
if ( isFunctionName( lastToken ) || inFunction > 0 ) {
inFunction++;
}
beginLine = false;
if ( inFunction > 0 ) {
out();
}
else {
out();
if ( !afterByOrSetOrFromOrSelect ) {
indent++;
newline();
beginLine = true;
}
}
parensSinceSelect++;
}
private static boolean isFunctionName(String tok) {
final char begin = tok.charAt( 0 );
final boolean isIdentifier = Character.isJavaIdentifierStart( begin ) || '"' == begin;
return isIdentifier &&
!LOGICAL.contains( tok ) &&
!END_CLAUSES.contains( tok ) &&
!QUANTIFIERS.contains( tok ) &&
!DML.contains( tok ) &&
!MISC.contains( tok );
}
private static boolean isWhitespace(String token) {
return WHITESPACE.indexOf( token ) >= 0;
}
private void newline() {
result.append( "\n" );
for ( int i = 0; i < indent; i++ ) {
result.append( indentString );
}
beginLine = true;
}
}
public static void main(String[] args) {
String sql = new MySQLFormatter().format("select * from t_sss");
System.out.println(sql);
}
}
输出结果样例:
按以上步骤,添加相关文件一定可以实现mybatis的格式化打印,但是有一个小坑,可能会打印两遍日志,因为又一遍是没有格式化的。去掉默认打印的配置即可。
本人是用logback进行日志打印的。我去掉了logback配置文件的如下配置后,就只打印了以便格式化后的SQL语句。供参考。
上一篇文章:https://mp.csdn.net/postedit/91788348。介绍使用logback打印日志,也是填补上一篇说要格式化打印mybatis输出的SQL语句的坑。
参考资料:
https://blog.csdn.net/gnd15732625435/article/details/81626095
https://blog.csdn.net/zknxx/article/details/69070330
https://www.ibm.com/developerworks/cn/java/j-lo-p6spy/ p6spy配置文件详解
https://www.cnblogs.com/luodengxiong/p/6766357.html
https://www.cnblogs.com/plain-heart/p/5012728.html
https://blog.csdn.net/kwy15732621629/article/details/77924398
https://blog.csdn.net/kisscatforever/article/details/78784254