본문 바로가기

프로그래밍/Spring

@DataJpaTest 와 P6Spy 를 같이 사용하기

이 글에서 사용된 Source는 Github에 올려두었습니다. 사용된 DB는 H2 Database이며 관련 Database 스키마는 src/main/resources/sql 에 있는 schema.sql 파일을 보시면 됩니다. src/main/resources 에 있는 application.yaml 파일에서 spring.datasource 부분을 독자분들의 Database 환경에 맞춰서 적용하세요.

 

SpringFramework를 이용하는 개발을 진행하면서 SQL 문을 보기 위해 사용했던 라이브러리로 log4jdbc-log4j2 라이브러리를 주로 사용했었다. 그러나 okky를 통해 p6spy를 알게 되면서 한번 이 라이브러리를 적용해볼려고 기존에 공부용으로 만들어놓은 프로젝트에 이 라이브러리를 적용하게 되었다. log4jdbc-log4j2 라이브러리에서 p6spy로 바꿔 갈아타게 되는 이유로 개발자들이 언급하는 것이 log4jdbc-log4j2 라이브러리라 2013년 이후로는 업데이트가 없다는 것이었다. 이 글을 쓰는 시점이 2021년인것을 감안하면 햇수만으로도 무려 8년 동안 업데이트가 없다는게 된다. 기능적인 업데이트는 없을수는 있겠지만 보안관련 업데이트가 없다는건 개발자 입장에서는 아무래도 찜찜하지 않을수 없다(극단적으로 보안에 문제가 발생한 사항이 없어서 업데이트가 없을수도 있겠으나 8년 동안 그런게 없었을까..에 대해서는 의구심은 드는것도 사실이다) 그리고 현업에서 개발하다보면 은근히 이런걸로 태클 거는 사람들이 있기도 하다. 그래서 이런걸로 트집잡히지 않기 위해서라도 한번 바꿔볼려고 손을 대게 되었다.

 

Spring Boot에서 p6spy를 적용하는 방법은 위에서 링크를 걸어놓은 p6spy 사이트를 통해서 관련 dependency를 설정해서 작업할 수 있겠으나 설정항목들이 복잡한 관계로 인해 적용하는 작업이 어려운 점이 있다. 그래서 spring boot starter 형태로 제공되는 p6spy-spring-boot-starter 를 이용하면 간편하게 적용할 수 있다. 이 starter 를 사용하면 application.properties 파일에 관련 설정을 하는 것으로 작업이 완료된다. 이 starter를 고집하게 된데는 그럴만한 이유가 있었는데 가장 큰 이유는 Database를 연결하기 위한 Driver과 연결 문자열을 바꾸지 않아도 SQL log를 출력한다는 것이다. SQL log를 출력해주는 라이브러리를 이용해보면 알겠지만 이 기능을 사용할려면 해당 라이브러리에서 제공하는 Driver Class와 별도의 양식으로 설정된 연결 문자열을 사용해야 한다. 이러한 라이브러리들의 원리는 Connection 객체를 통해 전송되는 SQL문을 가로채서 우리가 지정한 별도의 양식으로 이쁘게(?) 출력하는 것인데 Connection 객체에서 SQL문을 가져올수는 있어도 그것을 log 라이브러리와 연결하는 기능은 Java Connection 객체에 구현되어 있지 않기 때문에 이 Connection 객체를 받는 Proxy 객체를 하나 더 만들어서 그 Proxy 객체에서 log로 출력하는 기능을 구현하게 되는 것이다. 이러다보니 라이브러리가 제공하는 별도의 Driver Class와 이 Class를 사용하게 되는 연결문자열을 이용하게 되는 것이다. 그러나 이 p6spy-spring-boot-starter를 사용하게 되면 SQL Log를 출력한다고 application.properties 파일에 설정해 주면 별도의 Driver Class를 지정하거나 별도의 연결문자열로 바꿔주는 작업을 하지 않아도 이 starter가 자동으로 알아서 진행해주게 된다(이 starter를 만든 개발자가 datasource proxy starter도 같이 만들었기 때문에 이러한 작업이 가능하게 되었다)

 

그러나 실제로 적용하는 과정에서는 쉽지가 않았는데 가장 큰 이유는 @DataJpaTest 어노테이션을 적용한 테스트 코드에서는 SQL log를 출력하지 않는다는 것이었다. Spring Data JPA Repository 인터페이스만 만든뒤에 이 인터페이스에 대한 테스트 과정에서 SQL문 로그를 볼 수 있으면 개발하는데 쉬운 편의성을 제공해주지만 그러지를 못하는 것이었다. 구글에서 이와 관련되어 검색해보면 @DataJpaTest 어노테이션을 이용해서 출력하는 것은 거의 없었고 @DataJpaTest 어노테이션이 아닌 @SpringBootTest 어노테이션을 사용하는 글을 본것들이 있다(인프런에서 JPA 강의중이신 김영한 님도 @SpringBootTest 어노테이션을 사용하라고 언급(링크, 수강생의 질문중에 테스트 관련 질문입니다 란 제목의 질문에 대한 답신)하신것을 구글 검색을 통해 보게됨) 그러나 @SpringBootTest 어노테이션은 부적합한것이 이 어노테이션을 테스트에 적용하게 되면 application.properties 에 설정된 모든 설정이 테스트 환경에 전부 올라가게 된다. 만약 application.properties 파일에 web mvc 관련 설정도 되어 있으면 테스트시 web mvc 설정도 같이 올라가게 된다. JPA 관련 설정만 올라가서 빠르게 테스트를 마쳐야 하는데 테스트와는 전혀 상관없는 환경까지 같이 올라가게 되어 시간을 잡아먹게 되는 상황이 벌어지는 것이다. 하지만 위에서 언급한 김영한 님이 올려주신 답변이 나에게는 이 문제가 발생한 이유를 알 수 있게 해주었다. @DataJpaTest는 말 그대로 JPA 관련 테스트를 하기 위한 환경만 올라가게 되기 때문에 우리가 볼려고 하는 SQL log를 출력하기 위한 환경은 올라가지 않게 되는 것이다. 이 문제를 해결할려면 @DataJpaTest 어노테이션을 사용하면서 테스트 하는 시점에 SQL log를 출력하기 위한 환경이 올라가주면 해결되는 것이다. 그러면 어떻게 하면 이 환경을 올릴수 있을까?

 

구글로 검색하던 도중에 Spring Boot + Testcontainers + DbRider + P6spy = Testing Relational Databases 에서 힌트를 얻게 되었다. 이 글에서 @EnableSqlLogging 어노테이션을 만들어서 사용하게 되는데 이 어노테이션에서 다음의 어노테이션을 사용하게 된다.

 

@ImportAutoConfiguration(DataSourceDecoratorAutoConfiguration.class)

 

@ImportAutoConfiguration 어노테이션은 추측에는 자동환경설정 클래스를 Import 하는 기능이 있을 것이라는 생각이 들었다. 그리고 Import할 자동환경설정 클래스로 DataSourceDecoratorAutoConfiguration.class 를 사용하고 있는데 이 클래스가 application.properties 파일에서 사용하고 있는 DataSource를 Proxy 한 객체로 만들어주는 역할을 하는 클래스라는 감이 들었다. 그러면서 이야기가 술술 풀리게 되었다. 아까 위에서 언급한 내용을 다시 살펴보자. @DataJpaTest 어노테이션은 JPA 관련 테스트 환경만 올라간다고 언급했었다. JPA 관련 테스트를 할려면 필수적으로 2가지가 필요한데 하나는 DataSource이고 또 하나는 이 DataSource를 사용하는 EntityManager가 있어야 한다(@DataJpaTest는 Default로 H2 Memory Database를 이용하는 테스트 환경을 사용하기 때문에 우리가 실제 작업할 DataSource를 이용하게끔 할려면 별도의 작업 설정을 해야 한다. 여기서는 이미 그러한 설정까지 마쳤다고 가정한다.) 문제는 이 DataSource는 우리가 사용하게 되는 기본 DataSource를 사용하기 때문에 SQL log 를 출력하기 위한 DataSource Proxy 객체를 사용하지 않게 된다는데 있다. 그래서 SQL log 가 출력되지 않게 된 것이다. 바꿔말하면 DataSourceDecoratorAutoConfiguration.class 를 환경설정에서 사용해주면 SQL log 출력을 위한 DataSource Proxy 객체를 만들어 줄 것이고 그걸 이용해서 SQL log를 출력할 것이라는 결론에 이르렀다.

 

이 다음부터는 일사천리로 술술 풀리게 되었다. @DataJpaTest 어노테이션은 이러한 기능들이 없기 때문에 이 어노테이션을 사용하면서 위에서 언급한 @ImportAutoConfiguration 어노테이션을 같이 사용하면 되는 것이었다. 이를 위해 아래와 같이 @CustomDataJpaTest  어노테이션을 만들어서 이를 사용했다.

 

package com.terry.p6spytest.config.annotation;

import com.github.gavlyukovskiy.boot.jdbc.decorator.DataSourceDecoratorAutoConfiguration;
import com.terry.p6spytest.config.TestConfig;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@DataJpaTest(showSql = false)
@ImportAutoConfiguration(DataSourceDecoratorAutoConfiguration.class)
@Import(TestConfig.class)
@ActiveProfiles("test")
public @interface CustomDataJpaTest {

}

 

@DataJpaTest 어노테이션을 사용하면서 showSql 속성을 false로 줌으로써 Spring Data JPA가 기본으로 제공해주는 SQL 문 출력 기능을 사용하지 않도록 했다.@Import 어노테이션을 통해서 TestConfig.class 를 Import 해서 환경설정을 하고 있는데 이 TestConfig.class 가 하는 역할은 출력하고자 하는 SQL log의 포맷을 설정해주는 역할을 하게 된다(SQL log의 포맷을 설정하는 내용에 대해서는 아래에 있는 Spring Data JPA, Querydsl 로깅처리. p6spy pretty하게 설정하기 를 참조하기 바란다)

@ActiveProfiles 어노테이션을 통해 test 프로파일을 사용하도록 되어 있는데 src/test/resources 를 보면 test 프로파일에 사용되는 환경파일인 application-test.yaml 파일이 있다. application-test.yaml 파일에 p6spy 로그 출력을 사용할지의 여부 등을 지정하는 환경설정 내용(decorator.datasource.p6spy 항목의 하위항목들)이 있다. application-test.yaml 파일에 보면 datasource를 지정하지 않았는데 datasource는 src/main/resources 에 있는 application.yaml 에 정의된 datasource를 그대로 이용하게끔 했다. 다만 위에서 언급했던 H2 Memory Database가 아닌 application.yaml 에 설정된 datasource를 사용하도록 하기 위해 application-test.yaml 파일에 spring.test.database.replace 항목을 none로 설정했다.

 

이렇게 설정을 마친뒤 src/test/java/com/terry/p6spytest/repository 에 있는 MemberRepositoryTest 클래스의 Member_Condition_List_Test() 메소드를 실행해보면 아래와 같은 형태로 SQL log를 출력하는 것을 볼 수 있다.

출력되는 SQL log

참고한 사이트

- Spring Boot + Testcontainers + DbRider + P6spy = Testing Relational Databases

- Spring Data JPA, Querydsl 로깅처리. p6spy pretty하게 설정하기