JDBC SQL 구문에 클라이언트 정보 남기기 프로그래밍

DB 서버가 한 대 있고, 해당 서버에 접속해서 쿼리를 날리는 Java 웹 애플리케이션이 여러 개라고 해보자.
커머스 업체라고 했을 때 사용자 화면 애플리케이션, 상품 정보 관리 애플리케이션, 정산 애플리케이션 등이 별도로 존재하며 어떤 특정 DB에 모두 동시에 접속하는 경우가 발생한다는 것은 쉽게 예상할 수 있을 것이다.

DB 서버의 로그를 보니 Slow Query가 남아 있는데, 도대체 이 Slow Query를 호출한 애플리케이션이 무엇인지 어떻게 하면 빠르게 판단할 수 있을까?

제일 쉬운 방법은 SQL 구문에 주석으로 각 클라이언트(애플리케이션)의 정보를 남기는 것이다.
SQL은 /* 이런 저런 내용 */ 형태로 주석을 남기는 것을 지원한다.

Java 6/JDBC 4 Spec에서 바로 이러한 Client Info를 남길 수 있는 기법을 지원한다(Connection#setClientInfo). 나는 현재 MySQL만 사용하고 있지만 다른 많은 DB들도 이를 구현하고 있으리라 예상한다.

더 아래에서는 Hibernate 사용시 Interceptor를 통해 더 쉽게 처리하는 방법도 간단히 소개한다.

JDBC 4 Client Info
MySQL의 경우 5.1.10 버전부터 JDBC 4의 Client Info 스펙을 구현하고 있다. (단, MySQL Replication Driver는 아직도(2014년 3월) 이를 구현하지 못하고 있다.)

이 스펙을 구현한다고 해서 모든 JDBC 드라이버가 SQL의 주석으로 Client 정보를 남기는 것은 아니다. 하지만 MySQL은 JDBC4CommentClientInfoProvider를 Client Info 처리의 기본 구현체로 지정하고 있으면 이것이 바로 주석으로 정보를 남기는 역할을 한다. 이 구현체는 clientInfoProvider JDBC URL 파라미터를 통해 원하는 것으로 바꾸는 것이 가능하다.

일단은 아주 생짜 JDBC로 어떻게 작동하는지 코드로 보여주면 다음과 같다.
// 예외처리는 모두 무시했음. 모범적인 코드 예가 아니므로 실전에서 따라하지 말 것.
Class.forName("com.mysql.jdbc.Driver");

Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&user=root&password=root");

InetAddress localhost = InetAddress.getLocalHost();

connection.setClientInfo("ApplicationName", "JDBC4 Test Application");
connection.setClientInfo("ClientHostname", localhost.getHostName());
connection.setClientInfo("ClientAddress", localhost.getHostAddress());


Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("select 1");


위 코드를 실행하면 MySQL의 쿼리 로그에 다음과 같이 찍히게 된다. 즉, DBA가 Slow Query를 발견했다면 즉시 어느 애플리케이션의 어느 서버에서 온 쿼리인지 인지할 수 있다.
/* ApplicationName=JDBC4 Test Application, ClientHostname=kwon37xi-dev, ClientAddress=127.0.1.1 */ select 1


하지만 현대 애플리케이션에서 Connection을 매번 직접 생성하여 값을 설정하는 일은 하지 않는다. 특히 Spring을 사용한다면 Connection 객체는 사실상 구경도 할 필요가 없고, 할 수는 있더라도 안하는게 더 좋다.

BoneCP 커넥션 풀BoneCPConfig.java 설정에서 setClientInfo 메소드를 통해 미리 클라이언트 정보를 Properties 객체로 넣어둘 수 있는 기능을 지원하고 있다. 따라서 커넥션 풀 설정시 한 번만 해주면 모든 Connection에 대해 Client Info가 전달된다.

Hibernate Interceptor
Hibernate를 사용하는 경우(JPA를 통해서든 직접이든) Interceptor 인터페이스 중에서 onPrepareStatement 메소드 구현을 통해서 위와 같은 일을 하는게 가능하다.
이 경우 좀 더 유리한데, 왜냐하면 이 인터셉터 구현체는 모든 쿼리 요청마다 매번 호출되기 때문이다. 따라서 모든 쿼리 호출시 호출 당시의 정보(접속 URL, 호출한 클래스와 메소드 정보) 등도 함께 넣는게 가능하다.

onPrepareStatement는 특별히 키/값 쌍을 넣거나 하는 것이 아니라 DB에 전송하기 전의 SQL 구문을 직접 편집하는 것이므로 SQL 주석을 개발자가 직접 만들어 넣어주면 된다.

실제 구현은 EmptyInterceptor를 상속하여 다른 것은 무시하고 onPrepareStatement만 구현하는 것이 좋다.

JPA 사용시 hibernate.ejb.interceptor로 Interceptor 구현 클래스의 FQCN을 지정해주면 된다.

워낙 쉬워 코드 예를 들지는 않겠지만 한가지 팁을 주면, Spring MVC 사용시 Spring MVC Interceptor를 통해 현재 URL정보와 컨트롤러 클래스/메소드 정보를 수집해두고 이를 ThreadLocal을 통해서 Hibernate Interceptor에 전달하여 SQL 구문에 URL과 컨트롤러 클래스/메소드 정보까지 집어넣는 것이 가능해진다. SQL 구문을 보고나서 오류 발생 URL을 즉시 알 수 있어서 오류 대응 능력이 조금 높아질 것이다. 단, SQL 구문이 길어짐으로써 생기는 다른 단점들은 감안해야 한다.

그리고 마지막으로, 사용자가 직접 입력하거나 변조할 수 있는 정보를 Client Info로 절대로 넣지말라. 굳이 넣어야 겠다면 이 또한 SQL Injection의 하나로 공격 대상이 될 수 있으므로 철저히 검증해서 넣도록 주의해야 한다.

공유하기 버튼

 
 

Spring JavaConfig냐 XML 설정이냐 프로그래밍

Spring 설정을 할 때 JavaConfig를 사용하는 것이 좋을까 XML 설정을 하는 것이 좋을까?
흔히 JavaConfig를 사용하자고 하는 말하는 사람들은 Java로 작성하면 정적 언어 특징 덕분에 작성 중 컴파일을 통해 에러를 쉽게 찾고 리팩토링도 안전하게 할 수 있어서 실수 위험이 줄어들기 때문에 XML보다는 Java가 더 낫다고 말한다.

그럼 아래의 XML 설정과 JavaConfig 설정을 살펴보자.(참고로 나는 이제 Commons DBCP를 더이상 사용하지 않는다. BoneCP를 주로 사용하려고 한다.)
<bean id="datasource"
 class="org.apache.commons.dbcp.BasicDataSource"
    p:driverClassName="${jdbc.driverClassName}" 
    p:url="${jdbc.url}"
    p:username="${jdbc.username}" 
    p:password="${jdbc.password}"
    p:maxActive="${jdbc.maxActive}"
    p:maxWait="${jdbc.maxWait}"
    p:validationQuery="${jdbc.validationQuery}"
    p:testWhileIdle="${jdbc.testWhileIdle}"
    p:timeBetweenEvictionRunsMillis="${jdbc.timeBetweenEvictionRunsMillims}"/>

@Value("${jdbc.driverClassName}")
private String driverClassName;

@Value("${jdbc.url}")
private String url;

// .. 너무 길어 생략

@Bean
public DataSource datasource() {
    BasicDataSource datasource = new BasicDataSource();
    datasource.setDriverClassName(driverClassName);
    datasource.setUrl(url);
    datasource.setUsername(username);
    datasource.setPassword(password);
    datasource.setMaxActive(maxActive);
    datasource.setMaxWait(maxWait);
    datasource.setValidationQuery(validationQuery);
    datasource.setTestWhileIdle(testWhileIdle);
    datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);

    return datasource;
}


어떤게 가독성이 높고 합리적이라 생각되는가? 내가 보기엔 XML이 훨씬 가독성이 높고 합리적으로 보인다. 또한 과연 우리가 BasicDataSource 클래스를 클래스로 직접 참조하거나 리팩토링할 가능성은? 여기서는 Java의 정적인 특징이 아무 의미가 없다.

나도 안다. 이게 좀 극단적인 예라는 것을. 문제는 Spring으로 설정을 하다보면 실제로 설정 XML로 빼는 것들은 보통 개발자가 직접 만든 코드보다는 저런 인프라성 객체 생성일 경우가 더 많다는 점이다.

그렇다면 나는 JavaConfig에 반대하는가? 그렇지 않다.
다만 JavaConfig라고 부르기보다는 CodeConfig라고 불렀으면 좋겠다는 점이다.

최근에 나는 Gradle을 사용하여 XML이 아닌 Groovy 코드로 빌드를 설정한다. 또한 로깅 프레임워크도 Logback을 사용하며, 이 설정도 XML이 아닌 Groovy로 하고 있다.
Groovy든 Java든 언어를 떠나서, 코드로 하는 설정의 특징을 살펴보자.
  • 코드는 그룹지어 클래스 혹은 메소드로 이름을 지어 분리가 가능하다.
  • 코드는 중복을 그룹지어 중복이 아닌 것은 인자로 받고 중복 부분은 클래스 혹은 메소드로 만들 수 있다.
  • 코드는 상황에 따른 대응을 더욱 쉽게 할 수 있다.

코드 기반 설정의 장점은 위 특징들에 있는 것이지 "정적"성에 있는 것이 아니다.

예를들어 위의 BasicDataSource 생성의 경우 나라면 아래와 같이 Factory를 만들어 중복이 되는 "jdbc.*"부분을 인자로 내려보내어 한번에 처리하게 할 것이다. BasicDataSourceFactory 내부에서는 Spring의 Environment 객체를 주입 받아서 "jdbc.driverClassName", "jdbc.url" 등의 값을 자동으로 가져다가 BasicDataSource 객체를 구성한다.우리는 수십개의 DataSource 객체를 생성해야 하는데, 각각의 DataSource를 다음과 같은 코드로 줄였다.
@Bean
public BasicDataSourceFactory datasource() {
    return new BasicDataSourceFactory("jdbc");
}


저렇게 중복을 제거하지 않더라도 단지 그루핑을 해서 메소드로 빼는 것만으로도 가치가 있다.

ᅟweb.xml은 그 XML의 향연이 볼 때마다 사람을 정신 못차리게 만드는 힘이 있는데, Servlet 3.0부터 Java 코드로 web.xml을 대체할 수 있게 하고 있다.
이때도 리스너 하나, 필터하나, 서블릿 하나 등을 그냥 Java 코드로 나열하지 말고 그룹지어 메소드로 뺀 뒤에 해당 메소드를 호출하는 형태로 만들면 가독성이 확연히 높아지게 된다.

현재 우리 팀에서 관리하고 있는 Logback 설정에서 가장 많이 중복되는 것은 RollingFileAppender 이다. 이 중에서도 일별 Rolling이 가장 많이 쓰인다. 이 어펜더는 다양한 옵션을 가지는데 대부분 패턴이 유사하다. 그래서 공통적인 사항을 메소드로 뽑고서 사실 가장 간결할 때는 다음 수준으로 호출해도 되게 만들었다.
dailyRollingAppender(name: 'somelog', file: '/logs/some.log')


상황에 따른 유연한 대응력도 코드 기반이 줄 수 있는 커다란 장점 중에 하나이다. 일반적으로 설정은 Profile별 분할은 하더라도 모든 개발자들이 똑같이 사용하는 것이 보통이다. 즉, Development 프로필의 설정이 모든 개발자들에게 동일하다는 점이다.
하지만 로깅을 예로 들면 어떤 개발자는 특정 부분의 로그를 더 자세히 찍고 싶을 때도 있을 것이다. 이때 development 프로필의 로깅 설정을 바꿨다가 실수로 저장소에 Push해서 다른 개발자들로부터의 원성을 사본 경험이 다들 있을 것이다.
이런 경우에도 개발자가 시스템 프라퍼티 세팅으로 자신만의 로그 설정파일을 임시로 사용하게 해주는 등의 처리가 코드를 통한 설정을 사용하면 쉽게 가능하다.(실제로 이렇게 만들었다).

이러한 유연성은 Profile에 따라 달라지는 무언가를 만들 때도 비슷하게 적용된다.

내가 보기엔 설정이라는 것이 앞으로는 설정 키/값을 가진 파일을 제외하고는 코드 기반으로 바뀌게 될 것으로 보인다. 이미 많은 부분들이 그렇게 바뀌었고. 개인적인 바람이라면 키/값 설정 파일도 XML이나 Java의 Properties 파일을 벗어나서 YML이 됐으면 좋겠다.
가끔 Spring 설정을 JavaConfig로 바꾸자는 주장을 보면 그 논리가 Java의 정적인 특성에 치우쳐져 있는 경우가 많다. 이는 맨 위에서 보여줬던 예제만으로도 쉽게 반대에 부딪히게 된다.
코드 기반 설정의 장점을 충분히 활용할 수 있도록 하자.

Spring의 Java Config 사용시 주의할 점

공유하기 버튼

 
 

1 2 3 4 5 6 7 8 9 10 다음