在项目中一般都会把SQL打印出来,出现问题时,为了方便定位问题。
还有一个原因,很多时候我们以为日志打的够详细了,可是偏偏没加的那几行出问题了,把SQL打出来,哪怕代码里没有日志,也能根据SQL大概定位问题。
JPA打印SQL,只需要在application.properties
加一下配置就好。
logging.level.org.hibernate.SQL=debug
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=trace
复制代码
打出来的SQL,有点故意恶心人的感觉。这只是两个简单的表联接,而且因为是测试用的,表里字段就随便建了几个。正常业务中,两三个表联查,而且表中字段如果又多的话,那简直不能看。
不过还好,JPA没有做到不留余地,还留了一丝机会。JPA可以使用拦截器。
在application.properties
中配置
spring.jpa.properties.hibernate.session_factory.statement_inspector=com.ler.jpa.interceptor.JpaInterceptor
复制代码
实现org.hibernate.resource.jdbc.spi.StatementInspector
接口,在inspect
方法里可以获取到SQL。
@Override
public String inspect(String sql) {
return sql;
}
复制代码
如果什么都不做,SQL就是下面这样
select user0_.id as id1_1_0_, user0_.age as age2_1_0_, user0_.name as name3_1_0_,
addresses1_.user_id as user_id3_0_1_, addresses1_.id as id1_0_1_, addresses1_.id as id1_0_2_,
addresses1_.detail as detail2_0_2_, addresses1_.user_id as user_id3_0_2_ from user user0_ left
outer join address addresses1_ on user0_.id=addresses1_.user_id where user0_.id=?
复制代码
现在有了SQL,就剩下怎么处理了,其实可以使用正则表达式。
可以解析出表名,字段
找到所有的 AS
,这些是要被删除的。
找到了之后,接下来就简单了,就是字符串的处理。
最后的SQL是这样:
这个是最简化版的,SQL不能直接运行。
select id , age , name , user_id , id , id , detail , user_id from user left outer join address
on id=user_id where id=?
复制代码
还有一个这样的:
这个SQL保留了别名,而且替换参数后可以直接运行,可读性也大大增强。
select us.id , us.age , us.name , ad.user_id , ad.id , ad.id , ad.detail , ad.user_id
from user us left outer join address ad on us.id=ad.user_id where us.id=?
复制代码
当然最好还是能把参数和SQL拼在一起,这样直接复制就可以运行,生活不就又美好了吗?
JPA打印参数是在org.hibernate.type.descriptor.sql.BasicBinder
的bind
方法中,现在实在没想到怎么处理,欢迎大家讨论分享。
拦截器完整代码:
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.resource.jdbc.spi.StatementInspector;
/**
* @author lww
*/
@Slf4j
public class JpaInterceptor implements StatementInspector {
private static final String TABLE_NAME_FINDER = "([a-z]+[\\d]+(_)(\\.)[a-z]+)";
private static final String AS_FINDER = "((as)(\\s)[a-z]+([\\d|a-z]+(_)+)+)";
private static final String SQL_START = "select";
private static final Boolean SIMPLE = false;
private static final Integer SUB_TABLE_START = 0;
private static final Integer SUB_TABLE_END = 2;
@Override
public String inspect(String sql) {
String lowerSql = sql.toLowerCase().replace("\n", "");
if (!lowerSql.startsWith(SQL_START)) {
return sql;
}
Pattern table = Pattern.compile(TABLE_NAME_FINDER);
Matcher tableMatcher = table.matcher(lowerSql);
List<String> alianTableNames = new ArrayList<>();
while (tableMatcher.find()) {
String s = tableMatcher.group();
String[] split = s.split("\\.");
alianTableNames.add(split[0]);
}
if (SIMPLE) {
verySimpleSql(lowerSql, alianTableNames);
} else {
simpleSql(lowerSql, alianTableNames);
}
return sql;
}
/**
* 保留别名的SQL,可以运行
*/
private void simpleSql(String lowerSql, List<String> alianTableNames) {
for (String alianTableName : alianTableNames) {
lowerSql = lowerSql.replace(alianTableName, alianTableName.substring(SUB_TABLE_START, SUB_TABLE_END));
}
Pattern as = Pattern.compile(AS_FINDER);
Matcher asMatcher = as.matcher(lowerSql);
while (asMatcher.find()) {
String group = asMatcher.group();
lowerSql = lowerSql.replace(group, "");
}
log.warn("JpaInterceptor_simpleSql_lowerSql:{}", lowerSql);
}
/**
* 最简化sql,可能不能直接运行
*/
private void verySimpleSql(String lowerSql, List<String> alianTableNames) {
for (String alianTableName : alianTableNames) {
lowerSql = lowerSql.replace(alianTableName, "");
}
lowerSql = lowerSql.replace(".", "");
Pattern as = Pattern.compile(AS_FINDER);
Matcher asMatcher = as.matcher(lowerSql);
while (asMatcher.find()) {
String group = asMatcher.group();
lowerSql = lowerSql.replace(group, "");
}
log.warn("JpaInterceptor_inspect_lowerSql:{}", lowerSql);
}
}
复制代码
最后欢迎大家关注我的公众号,分享优质内容,共同学习,一起进步。加油🤣
南诏Blog
近期评论