Hibernate довольно интересный и кастомизируемый фреймворк. Одной из интересных его особенностей является возможность подключения так называемых интерцепторов - классов, реализующих интерфейс Interceptor, соответствующие методы которых будут вызываться до непосредственной работы с данными. Т.е. в интерцепторе можно изменять сгенерированные sql-запросы, поля у entity-объектов, вести учет колличества выполненных запросов, собирать статистику и делать другие не менее интересные вещи.
Сегодня суровый челябинский программист расскажет о том, как подключить свой интерцептор через Spring и о том, какие при этом возникают проблемы.
Как я уже сказал - интерцептор реализует интерфейс Interceptor (а лучше - является наследником класса EmptyInterceptor) и определяет некоторую логику изменения предзапросных данных. Например - интерцептор собирает статистику времени выполнения запроса, работы с кэшем и т.д., используя для этого встроенную систему статистики Hibernate, и выводит ее на экран. Особенностью такого интерцептора является то, что ему для работы нужна SessionFactory.
@SuppressWarnings("serial")
public class QueryStatisticsInterceptor extends EmptyInterceptor implements ISessionFactoryAwareInterceptor
{
private static final Logger _log = Logger.getLogger(QueryStatisticsInterceptor.class);
private static final String QUERY_INFO = "\nQuery: %s \n AvgExecutionTime: %s CacheHitTime: %s : CachePutTime: %s CacheMissTime: %s\n";
private SessionFactory _sessionFactory;
@Override
public void setSessionFactory(SessionFactory sessionFactory)
{
_sessionFactory = sessionFactory;
}
@Override
public String onPrepareStatement(String sql)
{
...
}
}
public class QueryStatisticsInterceptor extends EmptyInterceptor implements ISessionFactoryAwareInterceptor
{
private static final Logger _log = Logger.getLogger(QueryStatisticsInterceptor.class);
private static final String QUERY_INFO = "\nQuery: %s \n AvgExecutionTime: %s CacheHitTime: %s : CachePutTime: %s CacheMissTime: %s\n";
private SessionFactory _sessionFactory;
@Override
public void setSessionFactory(SessionFactory sessionFactory)
{
_sessionFactory = sessionFactory;
}
@Override
public String onPrepareStatement(String sql)
{
...
}
}
Итак, предположим, что мы написали интерцептор, теперь его надо подключить к нашему приложению. Используя Spring это сделать довольно просто - необходимо определить интерцептор как бин и указать ссылку на него в параметре entityInterceptor бина sesionFactory. Выглядеть это может, например, так:
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="hibernateProperties" ref="hibernateProperties" />
<property name="entityInterceptor" ref="queryStatisticsInterceptor" />
<property name="mappingResources">
<list>
</list>
</property>
</bean>
<bean
id="queryStatisticsInterceptor"
class="ru.naumen.core.hibernate.services.interceptors.QueryStatisticsInterceptor">
</bean>
<property name="dataSource" ref="dataSource" />
<property name="hibernateProperties" ref="hibernateProperties" />
<property name="entityInterceptor" ref="queryStatisticsInterceptor" />
<property name="mappingResources">
<list>
</list>
</property>
</bean>
<bean
id="queryStatisticsInterceptor"
class="ru.naumen.core.hibernate.services.interceptors.QueryStatisticsInterceptor">
</bean>
Но! В данном случае существует две основные проблемы:
- 1. Всегда в SessionFactory можно подключать только один интерцептор
- 2. В случае, если интерцептору нужна sessionFactory получается замкнутый круг, который Spring не может разрулить сам. Получается, что для создания sessionFactory нужен interceptor, а для создания interceptor - sessionFactory. В общем секса нет, потому что прыщи, а прыщи - потому что нет секса.
Первую проблему решить не сложно - достаточно создать интерцептор, который позволяет делегировать вызовы соответствующих методов другим интерцепторам, этакий интерцептор-обертку. У нас в компании это называется ComplexInterceptor, внутреннее его устройство оставлю вам в качестве домашнего задания, главное - чтобы он реализовывал интерфейс:
Вторая проблема решается довольно стандартным методом - пишется бин, который получает sessionFactory и complexInterceptor, а затем раздает всем сестрам по серьгам, т.е. устанавливает всем входящим в complexInterceptor интерцепторам sessionFactory (на самом деле не всем, а тем кому она нужна). Для того, чтобы произвести присваивание необходимо, чтобы интерцепторы реализовывали интерфейс ISessionFactoryAwareInterceptor:
public interface ISessionFactoryAwareInterceptor extends Interceptor
{
void setSessionFactory(SessionFactory sessionFactory);
}
{
void setSessionFactory(SessionFactory sessionFactory);
}
Собственно бин-связыватель может иметь следующий код:
public class SessionFactoryAwareInterceptorsAdvisor
{
public SessionFactoryAwareInterceptorsAdvisor(IComplexInterceptor interceptor,
SessionFactory sessionFactory)
{
Interceptor[] interceptors = interceptor.getInterceptors();
for (Interceptor interc : interceptors)
if (interc instanceof ISessionFactoryAwareInterceptor)
((ISessionFactoryAwareInterceptor) interc).setSessionFactory(sessionFactory);
}
}
{
public SessionFactoryAwareInterceptorsAdvisor(IComplexInterceptor interceptor,
SessionFactory sessionFactory)
{
Interceptor[] interceptors = interceptor.getInterceptors();
for (Interceptor interc : interceptors)
if (interc instanceof ISessionFactoryAwareInterceptor)
((ISessionFactoryAwareInterceptor) interc).setSessionFactory(sessionFactory);
}
}
И конечно же фрагмент Spring-конфига:
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="hibernateProperties" ref="hibernateProperties" />
<property name="entityInterceptor" ref="complexHibernateInterceptor" />
<property name="mappingResources">
<list>
</list>
</property>
</bean>
<bean
id="queryStatisticsInterceptor"
class="QueryStatisticsInterceptor">
</bean>
<bean
id="complexHibernateInterceptor"
class="ComplexInterceptor">
<constructor-arg>
<list value-type="org.hibernate.Interceptor">
<ref bean="queryStatisticsInterceptor" />
</list>
</constructor-arg>
</bean>
<bean
id="complexHibernateInterceptorAdvisor"
class="SessionFactoryAwareInterceptorsAdvisor">
<constructor-arg ref="complexHibernateInterceptor" />
<constructor-arg ref="sessionFactory" />
</bean>
<property name="dataSource" ref="dataSource" />
<property name="hibernateProperties" ref="hibernateProperties" />
<property name="entityInterceptor" ref="complexHibernateInterceptor" />
<property name="mappingResources">
<list>
</list>
</property>
</bean>
<bean
id="queryStatisticsInterceptor"
class="QueryStatisticsInterceptor">
</bean>
<bean
id="complexHibernateInterceptor"
class="ComplexInterceptor">
<constructor-arg>
<list value-type="org.hibernate.Interceptor">
<ref bean="queryStatisticsInterceptor" />
</list>
</constructor-arg>
</bean>
<bean
id="complexHibernateInterceptorAdvisor"
class="SessionFactoryAwareInterceptorsAdvisor">
<constructor-arg ref="complexHibernateInterceptor" />
<constructor-arg ref="sessionFactory" />
</bean>
Самое гланое - несмотря на то, что наш интерцептор имеет сеттер для SessionFactory его ненадо устанавливать при описании бина! Иначе Spring не сможет разрулить зависимости.
Понравилось сообщение - подпишись на блог
2 комментария:
Павел, спасибо за статью.
Как раз стоит проблема получения из интерцептора сессию.
Однако, не до конца понимаю некоторые моменты.
Подскажите, пожалуйста, как спринг посредством complexHibernateInterceptor в sessionFactory разруливает какой именно интерцептор ему использовать если их несколько?
Возможно более проще реализовать эту схему, в случае когда требуется только один интерцептор с доступом к sessionFactory (то-бишь без использования ComplexInterceptor)?
И, специально для двоишников, которые не выполнили домашнее задание :) Если можно, покажите пожалуйста исходник ComplexInterceptor.
Заранее благодарен.
Суть как раз в том, что ComplexInterceptor используется если в одном месте нужно вызвать несколько интерцепторов. Если хочется в каком-то месте вызвать один интерцептор, то особого смысла в ComplexInterceptor нет.
В коде там ничего сложного нет, просто сохраняем коллекцию интерцепторов и в каждом действии ее обходим и вызываем нужные методы.
Отправить комментарий
Любой Ваш комментарий важен для меня, однако, помните, что действует предмодерация. Давайте уважать друг друга!