JPA-打印的原生-SQL太恶心了,怎么办

在项目中一般都会把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.BasicBinderbind方法中,现在实在没想到怎么处理,欢迎大家讨论分享。

拦截器完整代码:

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