この記事では、@ Formula@ Where@ Filter、および@Anyアノテーションを使用したHibernateの動的マッピング機能について説明します。




 org.hibernate hibernate-core 5.4.12.Final   com.h2database h2 1.4.194 


3. @ Formulaを使用して計算された列


@Entity public class Employee implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private long grossIncome; private int taxInPercents; public long getTaxJavaWay() { return grossIncome * taxInPercents / 100; } }


すでに計算された値をデータベースから取得する方がはるかに簡単です。これは、@ Formulaアノテーションを使用して実行できます。

@Entity public class Employee implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private long grossIncome; private int taxInPercents; @Formula("grossIncome * taxInPercents / 100") private long tax; }

@Formula、我々は、サブクエリを使用して、ネイティブデータベース機能とストアドプロシージャを呼び出し、基本的にはこのフィールドのSQL select句の構文を破壊しない何かを行うことができます。



Employee employee = new Employee(10_000L, 25); session.save(employee); session.flush(); session.clear(); employee = session.get(Employee.class, employee.getId()); assertThat(employee.getTax()).isEqualTo(2_500L);

4. @ Whereを使用したエンティティのフィルタリング




@Entity @Where(clause = "deleted = false") public class Employee implements Serializable { // ... }


employee.setDeleted(true); session.flush(); session.clear(); employee = session.find(Employee.class, employee.getId()); assertThat(employee).isNull();

@Formulaアノテーションの場合と同様に、生のSQLを処理しているため、エンティティをデータベースにフラッシュしてコンテキストから削除するまで、@ Where条件は再評価されません



@Entity public class Phone implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private boolean deleted; private String number; }


public class Employee implements Serializable { // ... @OneToMany @JoinColumn(name = "employee_id") @Where(clause = "deleted = false") private Set phones = new HashSet(0); }


employee.getPhones().iterator().next().setDeleted(true); session.flush(); session.clear(); employee = session.find(Employee.class, employee.getId()); assertThat(employee.getPhones()).hasSize(1); List fullPhoneList = session.createQuery("from Phone").getResultList(); assertThat(fullPhoneList).hasSize(2);

5. @ Filterを使用したパラメータ化されたフィルタリング


The @Filter annotation works the same way as @Where, but it also can be enabled or disabled on session level, and also parameterized.

5.1. Defining the @Filter

To demonstrate how @Filter works, let's first add the following filter definition to the Employee entity:

@FilterDef( name = "incomeLevelFilter", parameters = @ParamDef(name = "incomeLimit", type = "int") ) @Filter( name = "incomeLevelFilter", condition = "grossIncome > :incomeLimit" ) public class Employee implements Serializable {

The @FilterDef annotation defines the filter name and a set of its parameters that will participate in the query. The type of the parameter is the name of one of the Hibernate types (Type, UserType or CompositeUserType), in our case, an int.

The @FilterDef annotation may be placed either on the type or on package level. Note that it does not specify the filter condition itself (although we could specify the defaultCondition parameter).

This means that we can define the filter (its name and set of parameters) in one place and then define the conditions for the filter in multiple other places differently.

This can be done with the @Filter annotation. In our case, we put it in the same class for simplicity. The syntax of the condition is a raw SQL with parameter names preceded by colons.

5.2. Accessing Filtered Entities

Another difference of @Filter from @Where is that @Filter is not enabled by default. We have to enable it on the session level manually, and provide the parameter values for it:

session.enableFilter("incomeLevelFilter") .setParameter("incomeLimit", 11_000);

Now suppose we have the following three employees in the database:

session.save(new Employee(10_000, 25)); session.save(new Employee(12_000, 25)); session.save(new Employee(15_000, 25));

Then with the filter enabled, as shown above, only two of them will be visible by querying:

List employees = session.createQuery("from Employee") .getResultList(); assertThat(employees).hasSize(2);

Note that both the enabled filter and its parameter values are applied only inside the current session. In a new session without filter enabled, we'll see all three employees:

session = HibernateUtil.getSessionFactory().openSession(); employees = session.createQuery("from Employee").getResultList(); assertThat(employees).hasSize(3);

Also, when directly fetching the entity by id, the filter is not applied:

Employee employee = session.get(Employee.class, 1); assertThat(employee.getGrossIncome()).isEqualTo(10_000);

5.3. @Filter and Second-Level Caching

If we have a high-load application, then we'd definitely want to enable Hibernate second-level cache, which can be a huge performance benefit. We should keep in mind that the @Filter annotation does not play nicely with caching.

The second-level cache only keeps full unfiltered collections. If it wasn't the case, then we could read a collection in one session with filter enabled, and then get the same cached filtered collection in another session even with filter disabled.

This is why the @Filter annotation basically disables caching for the entity.

6. Mapping Any Entity Reference With @Any

Sometimes we want to map a reference to any of multiple entity types, even if they are not based on a single @MappedSuperclass. They could even be mapped to different unrelated tables. We can achieve this with the @Any annotation.

In our example, we'll need to attach some description to every entity in our persistence unit, namely, Employee and Phone. It'd be unreasonable to inherit all entities from a single abstract superclass just to do this.

6.1. Mapping Relation With @Any

Here's how we can define a reference to any entity that implements Serializable (i.e., to any entity at all):

@Entity public class EntityDescription implements Serializable { private String description; @Any( metaDef = "EntityDescriptionMetaDef", metaColumn = @Column(name = "entity_type")) @JoinColumn(name = "entity_id") private Serializable entity; }

The metaDef property is the name of the definition, and metaColumn is the name of the column that will be used to distinguish the entity type (not unlike the discriminator column in the single table hierarchy mapping).

We also specify the column that will reference the id of the entity. It's worth noting that this column will not be a foreign key because it can reference any table that we want.

The entity_id column also can't generally be unique because different tables could have repeated identifiers.

The entity_type/entity_id pair, however, should be unique, as it uniquely describes the entity that we're referring to.

6.2. Defining the @Any Mapping With @AnyMetaDef

Right now, Hibernate does not know how to distinguish different entity types, because we did not specify what the entity_type column could contain.

To make this work, we need to add the meta-definition of the mapping with the @AnyMetaDef annotation. The best place to put it would be the package level, so we could reuse it in other mappings.

Here's how the package-info.java file with the @AnyMetaDef annotation would look like:

@AnyMetaDef( name = "EntityDescriptionMetaDef", metaType = "string", idType = "int", metaValues = { @MetaValue(value = "Employee", targetEntity = Employee.class), @MetaValue(value = "Phone", targetEntity = Phone.class) } ) package com.baeldung.hibernate.pojo;



Employee employee = new Employee(); Phone phone1 = new Phone("555-45-67"); Phone phone2 = new Phone("555-89-01"); employee.getPhones().add(phone1); employee.getPhones().add(phone2);


EntityDescription employeeDescription = new EntityDescription( "Send to conference next year", employee); EntityDescription phone1Description = new EntityDescription( "Home phone (do not call after 10PM)", phone1); EntityDescription phone2Description = new EntityDescription( "Work phone", phone1);