Как получить запрос в 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();
}
}
}