Search

데이터베이스 쿼리 설정

Created
2022/10/16 01:17

1. p6spy

쿼리 로깅을 위해서 Hibernate에서 제공하는 기본 SQL 로깅을 이용해도 좋지만, 인자가 보이지 않는 점이 꽤나 불편하다. p6spy를 이용하여 이를 해결할 수 있다. 이전 항목에서 환경 분리를 위해 설정 파일을 나눴는데, dev 및 prod 환경에서는 쿼리 로깅을 하지 않도록 아래와 같은 값을 추가하도록 한다.
decorator: datasource: p6spy: enable-logging: false
Java
build.gradle의 depdencies에 아래 라이브러리를 추가한다.
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.8.1'
Java
기본적으로 지원하는 로깅이 꽤나 눈에 들어오지 않기 때문에, 아래 링크에서 제시한 방법대로 Strategy를 추가하여 로깅 방식을 변경한다. Strategy 코드는 아래와 같다. 작성된 코드는 더 아래에 기재할 @EnableQueryLog에 등록하여 이용한다.
@Component public class P6SpyFormatter extends JdbcEventListener implements MessageFormattingStrategy { @Override public void onAfterGetConnection(ConnectionInformation connectionInformation, SQLException e) { P6SpyOptions.getActiveInstance().setLogMessageFormat(getClass().getName()); } @Override public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql, String url) { StringBuilder sb = new StringBuilder(); sb.append(category).append(" ").append(elapsed).append("ms"); if (StringUtils.hasText(sql)) { sb.append(highlight(format(sql))); } return sb.toString(); } private String format(String sql) { if (isDDL(sql)) { return FormatStyle.DDL.getFormatter().format(sql); } else if (isBasic(sql)) { return FormatStyle.BASIC.getFormatter().format(sql); } return sql; } private String highlight(String sql) { return FormatStyle.HIGHLIGHT.getFormatter().format(sql); } private boolean isDDL(String sql) { return sql.startsWith("create") || sql.startsWith("alter") || sql.startsWith("comment"); } private boolean isBasic(String sql) { return sql.startsWith("select") || sql.startsWith("insert") || sql.startsWith("update") || sql.startsWith("delete"); } }
Java
매번 스프링 부트를 올려서 JPA를 이용하는 통합 테스트는 꽤나 무거운 작업이므로, JPA만 테스트 하길 원한다면 @DataJpaTest를 이용할 수 있다. 이 때, 테스트 환경에서의 로깅 설정이 별도로 필요한데, 이는 p6spy-spring-boot-starter 의 자동 설정이 적용되지 않아서 쿼리 로그가 출력되지 않기 때문이다. 문제를 해결하기 위해선 build.gradle에서 별도의 라이브러리 설치가 필요한데, 아래 코드를 dependencies에 추가하자.
implementation 'org.springframework.boot:spring-boot-test-autoconfigure'
Java
그리고 아래와 같이 @EnableQueryLog와 같은 어노테이션을 별도로 만들어서, 테스트 하려는 객체에 @DataJpaTest와 @EnableQueryLog를 함께 명시하면 테스트에서의 쿼리 로깅도 정상적으로 되는 것을 볼 수 있다.
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @ImportAutoConfiguration(DataSourceDecoratorAutoConfiguration.class) @Import(P6SpyFormatter.class) @TestPropertySource(properties = { "logging.level.org.springframework.test.context=ERROR" }) public @interface EnableQueryLog { @PropertyMapping("spring.jpa.show-sql") boolean showSql() default false; @PropertyMapping("decorator.datasource.p6spy.enable-logging") boolean enableLogging() default true; }
Java

2. QueryDSL

정적인 쿼리들은 Spring Data JPA의 Unnamed Query를 이용하여 컴파일 타임에 검사하도록 만들면 되지만, 그 외의 동적으로 구성되어야 하는 쿼리들은 이를 이용할 수 없다. 따라서 QueryDSL이라는 라이브러리를 활용하여 이를 해결한다.
QueryDSL을 이용하기 위해선 기존 객체를 활용해서 코드를 조작할 수 있는 형태의 객체를 만들어야 하는데, 이를 QType이라 부른다. QueryDSL 라이브러리를 설치하고나면 QType 객체들을 생성해야 하는데, 이들의 위치가 git에 영향을 받지 않도록 ignore된 build 아래에 위치되도록 설정도 해줘야 한다.
이와 같은 설정들은 build.gradle에서 이뤄지며, 전반적으로 추가되어야 하는 코드들은 아래와 같다.
plugins { ... // 추가 id "com.ewerk.gradle.plugins.querydsl" version "1.0.10" } configurations { ... // 추가 querydsl.extendsFrom compileClasspath } dependencies { ... // 추가 implementation 'com.querydsl:querydsl-apt' implementation 'com.querydsl:querydsl-jpa' ... } // 끝까지 추가 def querydslDir = "$buildDir/generated/querydsl" querydsl { library = "com.querydsl:querydsl-apt" jpa = true querydslSourcesDir = querydslDir } sourceSets { main.java.srcDir querydslDir } compileQuerydsl { options.annotationProcessorPath = configurations.querydsl }
Java
위 파일에 명시된 compileQuerydsl을 gradle 명령어로 활용할 수 있는데, 기존에 빌드된 QType 파일을 제거하고 새롭게 QType을 생성하기 위해선 아래 명령어를 이용하면 된다. 특히 새로운 엔티티를 만드는 경우엔 반드시 위 명령어를 활용하여 QType을 만들어줘야 문제가 생기지 않는다.
./gradlew clean compileQuerydsl

3. 동작 테스트

JPA 테스트에서 확인하고자 하는 것은 Spring Data JPA가 정상 동작하는지, QueryDSL은 정상 동작하는지, 그 과정에서 p6spy가 쿼리 로깅을 잘하고 있는지 여부이다.
test 경로에 repository를 아래와 같이 Spring Data JPA를 이용하도록 SampleRepository라는 인터페이스를 생성한다.
public interface SampleRepository extends JpaRepository<Sample, Long> { }
Java
QType의 생성은 @Entity가 main 경로에 있어야 정상적으로 수행되므로, main경로의 entity에 Sample이라는 객체를 생성한다.
@Entity @Getter @Setter @NoArgsConstructor public class Sample { @Id @GeneratedValue private Long id; }
Java
테스트 코드는 아래와 같이 작성한다. 테스트를 구동하면 정상적으로 통과 되는 것을 볼 수 있으며, 그 과정에서 Spring Data JPA, QueryDSL, p6spy가 정상적으로 동작하는 것도 볼 수 있다.
@SpringBootTest @Transactional @EnableQueryLog class SampleTest { @Autowired SampleRepository sampleRepository; @Autowired EntityManager em; @Test public void springDataJpa() { Sample sample = new Sample(); Sample savedSample = sampleRepository.save(sample); assertThat(sample).isEqualTo(savedSample); assertThat(sample.getId()).isEqualTo(savedSample.getId()); } @Test public void queryDsl() { JPAQueryFactory query = new JPAQueryFactory(em); Sample sample = new Sample(); em.persist(sample); em.flush(); Sample fetchedSample = query.selectFrom(QSample.sample).fetchOne(); assertThat(sample).isEqualTo(fetchedSample); assertThat(sample.getId()).isEqualTo(fetchedSample.getId()); } }
Java