之前写过一篇,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 共同学习,一起进步。加油🤣
近期评论