понедельник, 27 октября 2008 г.

Подключаем Hibernate Interceptor через Spring


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)

    {

      ...

    }

}


Итак, предположим, что мы написали интерцептор, теперь его надо подключить к нашему приложению. Используя 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>


Но! В данном случае существует две основные проблемы:

  • 1. Всегда в SessionFactory можно подключать только один интерцептор

  • 2. В случае, если интерцептору нужна sessionFactory получается замкнутый круг, который Spring не может разрулить сам. Получается, что для создания sessionFactory нужен interceptor, а для создания interceptor - sessionFactory. В общем секса нет, потому что прыщи, а прыщи - потому что нет секса.



Первую проблему решить не сложно - достаточно создать интерцептор, который позволяет делегировать вызовы соответствующих методов другим интерцепторам, этакий интерцептор-обертку. У нас в компании это называется ComplexInterceptor, внутреннее его устройство оставлю вам в качестве домашнего задания, главное - чтобы он реализовывал интерфейс:

public interface IComplexInterceptor

{

   

    public Interceptor[] getInterceptors();

}


Вторая проблема решается довольно стандартным методом - пишется бин, который получает sessionFactory и complexInterceptor, а затем раздает всем сестрам по серьгам, т.е. устанавливает всем входящим в complexInterceptor интерцепторам sessionFactory (на самом деле не всем, а тем кому она нужна). Для того, чтобы произвести присваивание необходимо, чтобы интерцепторы реализовывали интерфейс ISessionFactoryAwareInterceptor:

public interface ISessionFactoryAwareInterceptor extends Interceptor

{

    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);

    }

}

 


И конечно же фрагмент 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>


Самое гланое - несмотря на то, что наш интерцептор имеет сеттер для SessionFactory его ненадо устанавливать при описании бина! Иначе Spring не сможет разрулить зависимости.

Понравилось сообщение - подпишись на блог

2 комментария:

samsonych комментирует...

Павел, спасибо за статью.

Как раз стоит проблема получения из интерцептора сессию.

Однако, не до конца понимаю некоторые моменты.

Подскажите, пожалуйста, как спринг посредством complexHibernateInterceptor в sessionFactory разруливает какой именно интерцептор ему использовать если их несколько?

Возможно более проще реализовать эту схему, в случае когда требуется только один интерцептор с доступом к sessionFactory (то-бишь без использования ComplexInterceptor)?

И, специально для двоишников, которые не выполнили домашнее задание :) Если можно, покажите пожалуйста исходник ComplexInterceptor.

Заранее благодарен.

Pavel Samolisov комментирует...

Суть как раз в том, что ComplexInterceptor используется если в одном месте нужно вызвать несколько интерцепторов. Если хочется в каком-то месте вызвать один интерцептор, то особого смысла в ComplexInterceptor нет.

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

Отправить комментарий

Любой Ваш комментарий важен для меня, однако, помните, что действует предмодерация. Давайте уважать друг друга!