[JDBC] Datasource Proxy 에 대하여
JPA 구현체인 Hibernate 의 문제점
Java 의 ORM 인 JPA(Java Persistence API)와 그 구현체 중 하나인 Hibrernate 를 사용하다보면 몇 가지 불편사항이 생긴다.
- Native Query 를 사용하지 않으면 (JPA) 쿼리 실행 계획이 어떻게 되는지 알 수 없다
- Hibernate debug log 를 이용하면 Bulk insert 여부를 알 수 없다
- Hibernate debug log 를 이용하면 Binding variable 파악을 위한 불편이 생긴다
- Query 의 execute 결과를 계획하기 어렵다
사실 어떻게 보면 단편적 혹은 극단적으로 작성한 사항일 수 있다.
하지만 비즈니스 로직을 작성하다보면 위 같은 사항들이 얼마나 불편한지 알게된다.
어떻게든 해결하는 방법은 있다.
예를들면 Binding variable 같은 문제는
INSERT INTO TBL_TEST VALUES ( ?, ?, ? )
logging: level: org: hibernate: type: descriptor: sql: trace
로 해결한다던지….
properties 나 log4j configuration class 를 바꾸는 등
어차피 소스코드를 변경해야한다면 한 번에 해결하는편이 좋지 않을까?
위에 나열한 대부분의 단점을 해결할 수 있는 방법이 있다.
Datasource Proxy 란
Java 에서 ‘우아하게’ (혹은 그럴듯하게) DB에 접근하는 과정은 다음과 같다.
Application -> DataSource -> JDBC Driver -> Physical DB
풀이하자면,
Application 에서는
DataSource 로부터 Connection 정보를 설정하고
DB Connectivity Driver 를 이용해
Physical DB 에 연결한다.
이 DataSource 와 Application 사이에 Proxy 를 두는것이다
연결을 가로채어 Persistence 와 Gathering 등 Database 와의 일련의 모든 로직들을 까볼 수 있다.
다음과 같이 말이다.
Application -> DataSourceProxy -> DataSource -> JDBC Driver -> Physical DB
DataSourceProxy 설정방법
먼저 DataSourceProxy 는 Spring 내에 기본적으로 탑재돼있지 않기 때문에 별도의 의존성이 필요하다.
본 글에서는 ttddyy 님의 datasource-proxy (https://github.com/ttddyy/datasource-proxy) 를 이용하도록 한다. Github의 README.md 를 참고하여 dependency 설정을 해주도록 한다. (maven, gradle 등 빌드툴에 맞게)
이후 DataSource 를 Bean 을 설정하는 부분을 DataSourceProxy 객체를 반환하도록 작성해야 한다.
예를들어 Spring 에서는 기본 DataSource 를 Bean 을 등록할 때,
@Bean public DataSource dataSource() { DataSource dataSource = DataSourceBuilder.create() .url(...) .username(...) .password(...) .build() return dataSource; }
위 처럼 Configuration class 에 method 를 작성한다.
이 DataSource 를 반환하는 method 에서 DataSourceProxy 객체를 반환하도록, 아주 약간만 수정해주면 된다.
@Bean public DataSource dataSource() { DataSource dataSource = DataSourceBuilder.create() .url(...) .username(...) .password(...) .build() DataSourceProxy dataSourceProxy = ProxyDataSourceBuilder .create(dataSource) .name("dataSource") .logQueryToSysOut() .asJson() .countQuery() .afterQuery((execInfo, queryInfoList) -> { System.out.println("Elapsed time : " + execInfo.getElapsedTime() + " ms\n"); }) .build(); return dataSourceProxy; }
정말 간단하게, DataSourceProxyBuilder 에 이전에 생성한 dataSource 객체를 담고 qualifier (bean name) 을 지정해준다.
이후 필요한 설정들을 빌더에 추가로 덧붙여준다. 아래는 내가 사용하는 설정이다.
- logQueryToSysOut() : System.out 으로 수행된 query 를 출력한다.
e.g.){“name”:”dataSource”, “connection”:5, “time”:85, “success”:true, “type”:”Prepared”, “batch”:true, “querySize”:1, “batchSize”:100, “query”: ~~~~
- asJson() : 위 출력될 query 데이터의 형식을 json 형식으로 지정한다.
- countQuery() : 실행된 query 의 count 를 보여준다.
- afterQuery() : query 가 실행된 이후의 동작을 지정한다. Java8 의 Consumer 를 받는다.
Consume 을 위해 제공되는 객체는 ExecInfo 와 QueryInfoList 2개이다. (위 코드 참고)
보통 query elasped time 을 찍는다.
위 설정을 마치고 query 가 수행되면 아래와 같이 로그가 남을것이다.
{"name":"dataSource", "connection":5, "time":85, "success":true, "type":"Prepared", "batch":true, "querySize":1, "batchSize":100, "query": [~~~~ ]} Elapsed time : ~~ms
말 그대로 로그이니, 디버깅에 사용하고 싶다면 JSON Beautifier 등을 이용해 이쁘게 보도록 하자
주의사항
이 DataSourceProxy 을 이용한 콘셉트는 DataSource 위에 추상계층이 있는 것이다.
따라서 ORM(여기서는 JPA) 를 구현한 라이브러리를 이용할때는 해당 로그를 꺼야, 이중으로 로그가 나오는 것을 막는다
예를 들어 hibernate 라면 application properties 에서
# show_sql: true # use_sql_comments: true # format_sql: true
다음 3줄을 코멘트 처리하거나 지워주도록 하자.
참고문헌
- https://vladmihalcea.com/the-best-way-to-log-jdbc-statements/ – The best way to log JDBC statements
2개의 댓글