之前写过一篇,JPA 打印的原生 SQL太恶心了,怎么办。
使用JPA拦截器和正则,来打印稍微好看一点的SQL。
打印JPA的原生SQL:
#原生打印sql
#spring.jpa.show-sql=true
logging.level.org.hibernate.SQL=debug
#spring.jpa.properties.hibernate.format_sql=true
#打印参数
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=trace
spring.jpa.show-sql和logging.level.org.hibernate.SQL效果一样。打印SQL如下:
select user0_.id as id1_1_0_, user0_.age as age2_1_0_, user0_.name
as name3_1_0_, user0_.version as version4_1_0_ from user user0_ where user0_.id=?
spring.jpa.properties.hibernate.format_sql这个是格式化SQL,效果如下:
select
user0_.id as id1_1_0_,
user0_.age as age2_1_0_,
user0_.name as name3_1_0_,
user0_.version as version4_1_0_
from
user user0_
where
user0_.id=?
格式个毛线,并没有变很漂亮...
使用自定义拦截器
spring.jpa.properties.hibernate.session_factory.statement_inspector=com.ler.demo.interceptor.JpaInterceptor自定义拦截器后,效果如下:
select us.id , us.age , us.name , us.version from user us where us.id=?
本来关于JPA也就告一段落了,对于JPA,实在喜欢不起来。
一个神奇的BUG,让我只能硬着头皮来再一次优化这个拦截器。
在JPA里,save和update是合二为一的,而这个项目的代码也比较奇葩,各种继承,各种嵌套,各种绑定,然后没有this,没有super,一个空荡荡的save(),变着法的写出难以阅读的代码,不知道什么仇什么怨...
而且JPA的特性还比较恶心,嵌套、绑定更新和保存是同步的,而且数据库里还有乐观锁,本来莫名其妙的好好的,可是有一个方法,莫名其妙的乐观锁冲突了。看着打印的原生的SQL,一股恶心之感升起。
如果能打印出SQL的调用堆栈就好了。
####具体方法:
private static void defaultTag(String startTag) {
List<String> logList = new ArrayList<>();
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (StackTraceElement e : stackTrace) {
if (!e.getClassName().startsWith(startTag) || e.getClassName().endsWith("JpaInterceptor")) {
continue;
}
//获取使用该工具类的代码所在的方法,java文件名,以及行号,
//并拼装成 MethodName(FileName.java:LineNumber) 这样的格式,
//前边的MethodName是辅助信息,后边的是用于让开发工具识别能够跳转的信息。
logList.add(String.format("JpaInterceptor_class: %s.%s (%s:%s)", e.getClassName(), e.getMethodName(), e.getFileName(), e.getLineNumber()));
}
if (logList.size() > 5) {
logList = logList.subList(0, 5);
}
Collections.reverse(logList);
logList.forEach(log::warn);
}
只要在inspect方法入口那里调用这个方法就可以
@Override
public String inspect(String sql) {
defaultTag("com.ler.demo");
...
}
参数是顶级包名,过滤掉其他的堆栈信息。
而且使用MethodName(FileName.java:LineNumber)这样的格式,在控制台中,可以直接跳转到代码中的位置。

括号里是蓝色的,点击可以直接跳转到对应的代码。
这里因为是测试代码,比较简单,调用链也少,在那个项目里调用链基本都是七八层。如果很长的话,可以设置深度,我这里设置为5,一般就可以看到从接收到请求,到执行SQL的全过程了。
还有这里使用了reverse方法,因为是逆序查找的堆栈,所以要再逆序一下。
当然这个不光可以在JPA拦截器里使用,如果你想看一个方法的调用链,也可以在方法入口处加上这个方法。
对啦,全局异常拦截器虽然很好用,可是一般只打一个异常信息,具体哪里出现的异常还得自己拿着异常信息定位,其实可以加上下面的代码,打印堆栈信息,可以直接定位到抛异常的地方:
List<StackTraceElement> stackTraceElements = Arrays.asList(ex.getStackTrace());
if (stackTraceElements.size() > MAX_DEPTH) {
stackTraceElements = stackTraceElements.subList(0, MAX_DEPTH);
}
ex.setStackTrace(stackTraceElements.toArray(new StackTraceElement[stackTraceElements.size()]));
LOGGER.error("handleException, ex caught, uri={}, httpResult={}", request.getRequestURI(), JSON.toJSONString(httpResult), ex);
效果如下:

好啦,今天就这样啦
最后欢迎大家关注我的公众号,南诏Blog 共同学习,一起进步。加油🤣