Глава 19. Фильтрация данных

Оглавление
  1. 19.1. Фильтры Hibernate

Hibernate предоставляет инновационный подход к обработке данных с помощью правил «видимости». Фильтр Hibernate является глобальным, именованным, параметризированным фильтром, который может быть включён или отключён для определенной сессии Hibernate.

19.1. Фильтры Hibernate

Hibernate имеет возможность предварительно определить критерии фильтра и присоединить эти фильтры как на уровне класса, так и на уровне коллекции. Критерии фильтра позволяют вам определить секцию ограничения, подобное существующему атрибуту «where», доступному для класса и различных элементов коллекции. Однако эти условия фильтра могут быть параметризованы. Затем приложение может решить во время работы, должны ли быть включены определенные фильтры и каковы должны быть их значения параметров. Фильтры могут использоваться как представления базы данных, но они параметризуются внутри приложения.

Использование аннотаций фильтров определяется через @org.hibernate.annotations.FilterDef или @org.hibernate.annotations.FilterDefs. Определение фильтра имеет имя name() и массив параметров. Параметр позволяет настроить поведение фильтра во время выполнения. Каждый параметр определяется @ParamDef, который имеет имя и тип. Вы также можете определить параметр defaultCondition() для данного @FilterDef, чтобы установить условие по умолчанию, которое будет использоваться, если в каждом отдельном @Filter не определены. @FilterDef() могут быть определены на уровне класса или пакета.

Теперь нам нужно определить секцию SQL-фильтра, применяемое к загрузке сущности или загрузке к коллекции. @Filter используется и помещается либо на сущность, либо на элемент коллекции. Соединение между @FilterName и @Filter является совпадающим именем.

Пример 19.1. Аннотации @FilterDef и @Filter

@Entity
@FilterDef(name="minLength", parameters=@ParamDef( name="minLength", type="integer" ) )
@Filters( {
    @Filter(name="betweenLength", condition=":minLength <= length and :maxLength >= length"),
    @Filter(name="minLength", condition=":minLength <= length")
} )
public class Forest { ... }

Когда коллекция использует таблицу ассоциаций в качестве реляционного представления, вы можете применить условие фильтра к самой таблице ассоциаций или к таблице целевых сущностей. Чтобы применить ограничение к целевой сущности, используйте стандартную аннотацию @Filter. Однако, если ваша цель - таблица ассоциаций, используйте аннотацию @FilterJoinTable.

Пример 19.2. Использование @FilterJoinTable для фильтрации в таблице ассоциаций

@OneToMany
@JoinTable
//filter on the target entity table
@Filter(name="betweenLength", condition=":minLength <= length and :maxLength >= length")
//filter on the association table
@FilterJoinTable(name="security", condition=":userlevel >= requredLevel")
public Set getForests() { ... }

По умолчанию Hibernate пытается автоматически определить все точки внутри фрагмента условия @Filter, в которые должен быть введен псевдоним. Чтобы управлять внедрением псевдонима, установите значение deduceAliasInjectionPoints в false в @Filter. Точки внедрения затем помечаются с помощью аннотаций @SqlFragmentAlias или в фрагменте условия SQL с использованием {alias}.

В дополнение к разрешению явного управления псевдонимами, deduceAliasInjectionPoints предоставляет выход, когда Hibernate предполагает, что зарезервированное ANSI SQL ключевое слово является столбцом и неправильно связывает его с псевдоним.

Пример 19.3. Аннотации @Filter, отключение deduceAliasInjectionPoints

@Entity
@Table(name="T_TREE")
@Filters({
    @Filter(name="isTall", condition="{alias}.LENGTH >= 100", deduceAliasInjectionPoints = false),
    @Filter(name="isOak", condition="{t}.WOODTYPE like 'oak'", deduceAliasInjectionPoints = false,
        aliases={@SqlFragmentAlias(alias="t", table="T_TREE")})
})
public class Tree { ... }

С использованием файлов отображения Hibernate для определения фильтров ситуация очень похожая. Сначала фильтры должны быть определены, а затем привязаны к соответствующим элементам отображения. Чтобы определить фильтр, используйте элемент <filter-def /> внутри элемента <hibernate-mapping />:

Пример 19.4. Определение фильтра через <filter-def>

<filter-def name="myFilter">
    <filter-param name="myFilterParam" type="string"/>
</filter-def>

Затем этот фильтр можно присоединить к классу или коллекции (или к обоим или многократно к каждому одновременно):

<class name="myClass" ...>
    ...
    <filter name="myFilter" condition=":myFilterParam = MY_FILTERED_COLUMN"/>
    <set ...>
        <filter name="myFilter" condition=":myFilterParam = MY_FILTERED_COLUMN"/>
    </set>  
</class>

Методами Session являются: enableFilter(String filterName), getEnabledFilter(String filterName) и disableFilter(String filterName). По умолчанию фильтры не включены для данной сессии. Фильтры должны быть активированы с помощью метода Session.enableFilter(), который возвращает экземпляр интерфейса Filter. Если вы использовали простой фильтр, определённый выше, он будет выглядеть так:

session.enableFilter("myFilter").setParameter("myFilterParam", "some-value");

Методы интерфейса org.hibernate.Filter позволяют создать цепочку методов (method-chaining) с большинством методов Hibernate.

Ниже приведен полный пример использования временных данных с эффективным шаблоном даты записи:

<filter-def name="effectiveDate">
    <filter-param name="asOfDate" type="date"/>
</filter-def>
<class name="Employee" ...> ... <many-to-one name="department" column="dept_id" class="Department"/> <property name="effectiveStartDate" type="date" column="eff_start_dt"/> <property name="effectiveEndDate" type="date" column="eff_end_dt"/> ... <!-- Обратите внимание, что это предполагает, что, для упрощения, нетерминальные записи имеют значение eff_end_dt установленое в максимальную дату db --> <filter name="effectiveDate" condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/> </class>
<class name="Department" ...> ... <set name="employees" lazy="true"> <key column="dept_id"/> <one-to-many class="Employee"/> <filter name="effectiveDate" condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/> </set> </class>

Чтобы убедиться, что вам предоставлены действующие записи, включите фильтр на сессии до получения данных о сотрудниках:

Session session = ...;
session.enableFilter("effectiveDate").setParameter("asOfDate", new Date());
List results = session.createQuery("from Employee as e where e.salary > :targetSalary")
         .setLong("targetSalary", new Long(1000000))
         .list();

Несмотря на то, что ограничение на зарплату было указано явно в вышеупомянутом HQL, из-за включенного фильтра запрос будет возвращать только текущих активных сотрудников, у которых зарплата превышает один миллион долларов.

Если вы хотите использовать фильтры с внешним соединением (outer join), либо через HQL, либо при загрузке выборки, будьте осторожны с направлением выражения условия. Это безопаснее установить для left outer join. Сначала поместите параметр, а затем имя столбца после оператора.

После определения фильтр может быть присоединен к нескольким сущностям и/или коллекциям, каждый со своим собственным условием. Это может быть проблематично, когда каждый раз условия одинаковы. Использование <filter-def /> позволяет определить условие по умолчанию либо как атрибут, либо CDATA:

<filter-def name="myFilter" condition="abc > xyz">...</filter-def>
<filter-def name="myOtherFilter">abc=xyz</filter-def>

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