HJW's IT Blog

JPA SQL - 가독성 좋게 보기 본문

SPRING

JPA SQL - 가독성 좋게 보기

kiki1875 2025. 2. 19. 16:36

0. 들어가며

 

JPA 를 사용하며 SQL문을 보기 위해 보통 applicaion.yml 의 spring.jpa.show-sql = true 설정을 쓸 것이다. 하지만.. 너무 가독성이 떨어진다. 디버깅 하기도 어렵고 어떤 쿼리가 발생하는지 읽기도 힘들다.

 

이번 포스팅에선 이러한 Hibernate 가 자동으로 찍어주는 쿼리 대신 P6Spy 라이브러리를 이용한 쿼리 커스터마이징에 대해 다루겠다.

 

1. 적용 전

Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.name as name3_0_ from user user0_ where user0_.name=?

 

정말 읽기 힘들고 가독성이 떨어지는 코드가 발생한다. 물론, format-sql = true 옵션을 주어 더 가독성 좋게 만들 수 있다. 

 

Hibernate: 
    select
        user0_.id as id1_0_,
        user0_.age as age2_0_,
        user0_.name as name3_0_ 
    from
        user user0_ 
    where
        user0_.name=?

 

하지만 여전히 어떤 인자들이 바인딩 되고, 각 변수가 무엇을 뜻하는지 읽기 힘들다. 

 

 

2. S6Spy 적용하기

P6Spy를 사용하여 SQL 로그를 출력하고, 로그 포맷을 커스터마이징하여 SQL을 더욱 쉽게 읽을 수 있다.

 

2.1 의존성 추가

implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.10.0'

 

2.2 SQL Format 클래스

 

public class P6spyPrettySqlFormatter implements MessageFormattingStrategy {  
  
  // ANSI 색상 코드  
  private static final String RESET = "\u001B[0m";  // 기본 색상  
  private static final String YELLOW = "\u001B[33m"; // SQL 키워드  
  private static final String WHITE = "\u001B[37m";  // 일반 텍스트  
  private static final String CYAN = "\u001B[36m";   // 구분선  
  
  // SQL 키워드 목록 (대문자로 변환하고 색상을 적용할 단어들)  
  private static final String SQL_KEYWORDS = "(?i)\\b(SELECT|FROM|WHERE|JOIN|INNER|LEFT|RIGHT|OUTER|ON|GROUP BY|ORDER BY|HAVING|AS|AND|OR|INSERT INTO|VALUES|UPDATE|SET|DELETE|LIMIT|OFFSET|DISTINCT)\\b";  
  
  @Override  
  public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql, String url) {  
    sql = formatSql(category, sql);  
    String currentTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());  
  
    return String.format(  
        "%s | %s[SQL 실행]%s\n%s\n\n%s|======================================================================|%s\n%s[실행 시간]: %d ms%s\n",  
        currentTime, YELLOW, RESET, sql, CYAN, RESET, YELLOW, elapsed, RESET  
    );  
  }  
  
  private String formatSql(String category, String sql) {  
    if (sql == null || sql.isBlank()) {  
      return sql;  
    }  
  
  
    if (Category.STATEMENT.getName().equals(category)) {  
      sql = FormatStyle.BASIC.getFormatter().format(sql);  
    }  
  
  
    Map<String, String> aliasMap = new HashMap<>();  
    AtomicInteger aliasCounter = new AtomicInteger(1);  
  
  
    Pattern pattern = Pattern.compile("\\b(\\w+?)(_\\d+)\\b");  
    Matcher matcher = pattern.matcher(sql);  
  
    StringBuilder sb = new StringBuilder();  
    while (matcher.find()) {  
      String originalAlias = matcher.group(); // ex: u1_0  
      String baseName = matcher.group(1); // ex: u  
  
      aliasMap.putIfAbsent(originalAlias, aliasMap.containsKey(baseName) ? baseName + aliasCounter.getAndIncrement() : baseName);  
      matcher.appendReplacement(sb, aliasMap.get(originalAlias));  
    }  
    matcher.appendTail(sb);  
  
    sql = sb.toString();  
  
  
    Pattern keywordPattern = Pattern.compile(SQL_KEYWORDS);  
    Matcher keywordMatcher = keywordPattern.matcher(sql);  
    StringBuffer formattedSql = new StringBuffer();  
  
    while (keywordMatcher.find()) {  
      String keyword = keywordMatcher.group().toUpperCase();  
      keywordMatcher.appendReplacement(formattedSql, YELLOW + keyword + RESET);  
    }  
    keywordMatcher.appendTail(formattedSql);  
  
    return WHITE + formattedSql.toString() + RESET;  
  }  
}

 

2.3 Configuration 등록

 

@Configuration  
public class P6spyConfig {  
  @PostConstruct  
  public void setLogMessageFormat() { 
  P6SpyOptions.getActiveInstance().setLogMessageFormat(P6spyPrettySqlFormatter.class.getName());  
  }  
}

 

2.4 application.yml 설정

 

decorator:  
  datasource:  
    p6spy:  
      enable-logging: true

 

 

3. 결과

 

훨씬 가독성 있고 어떤 인자가 넘어오는지 까지 한눈에 볼 수 있다.