Как получить запрос в MyBatis Interceptor

Я хочу измерить время выполнения sql, которое будет выполняться MyBatis (проект Spring Boot), и связать его с другими параметрами запроса, чтобы я мог получить полную информацию о проблемах производительности, связанных с конкретными запросами. Для этого случая я использовал MyBatis Interceptor следующим образом:

@Intercepts({
    @Signature(
            type = Executor.class,
            method = "query",
            args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
    @Signature(
            type = Executor.class,
            method = "query",
            args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class QueryMetricsMybatisPlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Stopwatch stopwatch = Stopwatch.createStarted();
        Object result = invocation.proceed();
        stopwatch.stop();
        logExectionTime(stopwatch, (MappedStatement) invocation.getArgs()[0]);
        return result;
    }
}

Теперь, когда дело доходит до привязки с запросом, я хочу сохранить эти метрики в запросе как атрибут. Я пробовал это простое решение для получения запроса, но оно не работало, так как запрос всегда был нулевым (я читал, что это решение не будет работать в асинхронных методах, но с MyBatis Interceptor и его методами я думаю, что это не так):

@Autowired
private HttpServletRequest request;

Итак, вопрос в том, как правильно получить запрос внутри перехватчика MyBatis?

1 ответ

Одно важное замечание, прежде чем я отвечу на ваш вопрос: плохой способ доступа к уровню пользовательского интерфейса на уровне DAO. Это создает зависимость в неправильном направлении. Внешние слои вашего приложения могут иметь доступ к внутренним слоям, но в этом случае все наоборот. Вместо этого вам нужно создать класс, который не принадлежит ни одному слою и будет (или, по крайней мере, может) использоваться всеми слоями приложения. Это можно назвать как MetricsHolder, Перехватчик может хранить значения для него, и в каком-то другом месте, где вы планировали получать метрики, вы можете читать из него (и использовать напрямую или сохранять их в запросе, если он находится на уровне пользовательского интерфейса и запрос доступен там).

Но теперь вернемся к вам вопрос. Даже если вы создаете что-то вроде MetricsHolder Вы все еще столкнетесь с проблемой, что вы не можете ввести его в перехватчик mybatis.

Вы не можете просто добавить поле с Autowired аннотацию к перехватчику и ожидать, что она будет установлена. Причина этого в том, что перехватчик создается mybatis, а не весной. Поэтому у Spring нет шансов ввести зависимости в перехватчик.

Один из способов справиться с этим - делегировать обработку перехвата в bean-компонент Spring, который будет частью контекста Spring и может получать доступ к другим bean-компонентам. Проблема здесь в том, как сделать этот компонент доступным в перехватчике.

Это можно сделать, сохранив ссылку на такой компонент в локальной переменной потока. Вот пример, как это сделать. Сначала создайте реестр, в котором будет храниться весенний боб.

public class QueryInterceptorRegistry {

    private static ThreadLocal<QueryInterceptor> queryInterceptor = new ThreadLocal<>();

    public static QueryInterceptor getQueryInterceptor() {
        return queryInterceptor.get();
    }

    public static void setQueryInterceptor(QueryInterceptor queryInterceptor) {
        QueryInterceptorRegistry.queryInterceptor.set(queryInterceptor);
    }

    public static void clear() {
        queryInterceptor.remove();
    }

}

Перехватчик запросов здесь выглядит примерно так:

public interface QueryInterceptor {
    Object interceptQuery(Invocation invocation) throws InvocationTargetException, IllegalAccessException;
}

Затем вы можете создать перехватчик, который будет делегировать обработку Spring bean:

@Intercepts({
        @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class }),
        @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}) })
public class QueryInterceptorPlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        QueryInterceptor interceptor = QueryInterceptorRegistry.getQueryInterceptor();
        if (interceptor == null) {
            return invocation.proceed();
        } else {
            return interceptor.interceptQuery(invocation);
        }
    }

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

    @Override
    public void setProperties(Properties properties) {
    }

}

Вам необходимо создать реализацию QueryInterceptor это делает то, что вам нужно, и делает его пружинным компонентом (вот где вы можете получить доступ к другому компоненту Spring, включая запрос, который является нет-нет, как я писал выше):

@Component
public class MyInterceptorDelegate implements QueryInterceptor {

    @Autowired
    private SomeSpringManagedBean someBean;

    @Override
    public Object interceptQuery(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
      // do whatever you did in the mybatis interceptor here
      // but with access to spring beans
   }
}

Теперь единственная проблема - установить и очистить делегата в реестре.

Я сделал это с помощью аспекта, который был применен к моим методам сервисного уровня (но вы можете сделать это вручную или в Spring Mvc Interceptor). Мой аспект выглядит так:

@Aspect
public class SqlSessionCacheCleanerAspect {

    @Autowired MyInterceptorDelegate myInterceptorDelegate;

    @Around("some pointcut that describes service methods")
    public Object applyInterceptorDelegate(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        QueryInterceptorRegistry.setQueryInterceptor(myInterceptorDelegate);
        try {
            return proceedingJoinPoint.proceed();
        } finally {
            QueryInterceptorRegistry.clear();
        }
    }

}
Другие вопросы по тегам