두 손끝의 창조자

MyBatis Sql 과 파라미터 출력하기 본문

프로그래밍

MyBatis Sql 과 파라미터 출력하기

codinglog 2021. 12. 20. 13:38

MyBatis는 특정 메소드를 인터셉트 할 수 있는 애노테이션인 Intercepts 을 제공한다.
Intercepts 의 값으로 메소드 시그니처를 지정해야하는데 메소드 시그니처를 지정하는 애노테이션이 Signature 이다.

메소드 시그니처는 메소드이름과 파라미터 타입이므로 인터셉트 할 메소드를 정확하게 지정한다. 예를 들어 org.apache.ibatis.executor.Executor 인터페이스는

  ...

  int update(MappedStatement ms, Object parameter) throws SQLException;
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

  ...

등의 오퍼레이션을 정의하고 있는데 위 오퍼레이션을 구현한 메소드를 인터셉트 하려면 아래 코드처럼 인터셉트 설정을 해야 한다.

 @Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
        , @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
}
)

메소드가 인터셉트되면 org.apache.ibatis.plugin.Invocation 클래스에 호출된 메소드와 컨텍스트를 담아서 사용할 수 있도록 해준다.

org.apache.ibatis.plugin.Invocation 인스턴스가 가지고 있는 데이터를 이용하여 로그를 남기는 코드를 작성하였다.

package com.dongkuk.dmes.cr.frm.access.log;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.reflect.Field;
import java.util.Map;
import java.util.Properties;

@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
        , @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
}
)
public class MybatisSqlLogger implements Interceptor {
    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MybatisSqlLogger.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        String queryID = ((MappedStatement) invocation.getArgs()[0]).getId();

        Object param = invocation.getArgs()[1];
        BoundSql boundSql = ((MappedStatement) invocation.getArgs()[0]).getBoundSql(param);

        String queryString = boundSql.getSql();

        log.info("\n/* {} */\n{}", queryID, queryString);

        for (int i = 0; i < boundSql.getParameterMappings().size(); i++) {
            ParameterMapping parameterMapping = boundSql.getParameterMappings().get(i);
            Object bindObject = getObject(boundSql.getParameterObject(), parameterMapping);

            log.info("binding parameter [{}] as [{}] - [{}]", i, bindObject.getClass().getName(), bindObject);

        }
        return invocation.proceed();
    }

    private Object getObject(Object parameterObject, ParameterMapping parameterMapping) throws IllegalAccessException, NoSuchFieldException {
        if (parameterObject instanceof Number || parameterObject instanceof String)
            return parameterObject;
        else if (parameterObject instanceof Map) {
            String property = parameterMapping.getProperty();
            return ((Map<?, ?>) parameterObject).get(property);
        } else {
            Class<?> aClass = parameterObject.getClass();
            String property = parameterMapping.getProperty();
            Field declaredField = null;
            try {
                declaredField = aClass.getDeclaredField(property);
            } catch (NoSuchFieldException e) {
                while (aClass.getSuperclass() != null) {
                    aClass = aClass.getSuperclass();
                    try {
                        declaredField = aClass.getDeclaredField(property);
                        break;
                    } catch (NoSuchFieldException ignored) {
                    }
                }
            }

            if (declaredField == null)
                throw new NoSuchFieldException(property);

            declaredField.setAccessible(true);
            return declaredField.get(parameterObject);
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}
반응형
Comments