Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
Tags
- OAuth 2.0
- nestjs
- middleware
- lombok
- factory
- 일급 컬렉션
- spring security
- synchronized
- Dependency Injection
- Volatile
- builder
- java
- 일급 객체
- Spring
- Google OAuth
Archives
- Today
- Total
HJW's IT Blog
JPA SQL - 가독성 좋게 보기 본문
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. 결과
훨씬 가독성 있고 어떤 인자가 넘어오는지 까지 한눈에 볼 수 있다.
'SPRING' 카테고리의 다른 글
JPA 다건 조회 시 프록시 객체가 포함되는 원인 분석 - feat. Persistence Context 출력하기 (0) | 2025.03.12 |
---|---|
Batch Fetching + Pagination으로 N + 1 해결하기 (0) | 2025.03.04 |
프론트 컨트롤러와 DispatcherServlet (0) | 2025.01.09 |
스프링 빈 초기화·소멸 로직 : 인터페이스, Bean, @PostConstruct (0) | 2024.12.12 |
컴포넌트 스캔과 의존성 자동 주입 (0) | 2024.12.04 |