@Intercepts({ @Signature(type = StatementHandler.class, method = "update", args = { Statement.class }),
		@Signature(type = StatementHandler.class, method = "query", args = { Statement.class, ResultHandler.class }) })
public class MybatisSqlLogInterceptor implements Interceptor {

	private static Logger sqlLog = LoggerFactory.getLogger("MYBATIS_SQL_LOG");

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		StatementHandler handler = (StatementHandler) invocation.getTarget();
		String sql = bindSql(handler); // SQL 추출

		sqlLog.info("=====================================================================");
		sqlLog.info("{} ", sql);
		sqlLog.info("=====================================================================");
		return invocation.proceed();
	}

	/**
	 * <pre>
	 * bindSql
	 *
	 * <pre>
	 *
	 * @param boundSql
	 * @param sql
	 * @param param
	 * @return
	 * @throws NoSuchFieldException
	 * @throws IllegalAccessException
	 */
	@SuppressWarnings("rawtypes")
	private String bindSql(StatementHandler handler) throws NoSuchFieldException, IllegalAccessException {
		BoundSql boundSql = handler.getBoundSql();

		// 쿼리실행시 맵핑되는 파라미터를 구한다
		Object param = handler.getParameterHandler().getParameterObject();
		// 쿼리문을 가져온다(이 상태에서의 쿼리는 값이 들어갈 부분에 ?가 있다)
		String sql = boundSql.getSql();

		// 바인딩 파라미터가 없으면
		if (param == null) {
			sql = sql.replaceFirst("\\?", "''");
			return sql;
		}

		// 해당 파라미터의 클래스가 Integer, Long, Float, Double 클래스일 경우
		if (param instanceof Integer || param instanceof Long || param instanceof Float || param instanceof Double) {
			sql = sql.replaceFirst("\\?", param.toString());
		}
		// 해당 파라미터의 클래스가 String인 경우
		else if (param instanceof String) {
			sql = sql.replaceFirst("\\?", "'" + param + "'");
		}
		// 해당 파라미터의 클래스가 Map인 경우
		else if (param instanceof Map) {
			List<ParameterMapping> paramMapping = boundSql.getParameterMappings();
			for (ParameterMapping mapping : paramMapping) {
				String propValue = mapping.getProperty();
				Object value = ((Map) param).get(propValue);
				if (value == null) {
					continue;
				}

				if (value instanceof String) {
					sql = sql.replaceFirst("\\?", "'" + value + "'");
				} else {
					sql = sql.replaceFirst("\\?", value.toString());
				}
			}
		}
		// 해당 파라미터의 클래스가 사용자 정의 클래스인 경우
		else {
			List<ParameterMapping> paramMapping = boundSql.getParameterMappings();
			Class<? extends Object> paramClass = param.getClass();

			for (ParameterMapping mapping : paramMapping) {
				String propValue = mapping.getProperty();
				Field field = paramClass.getDeclaredField(propValue);
				field.setAccessible(true);
				Class<?> javaType = mapping.getJavaType();
				if (String.class == javaType) {
					sql = sql.replaceFirst("\\?", "'" + field.get(param) + "'");
				} else {
					sql = sql.replaceFirst("\\?", field.get(param).toString());
				}
			}
		}

		// return sql
		return sql;
	}

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

	@Override
	public void setProperties(Properties properties) {
	}
}