14.3. Embedded and Associated Objects
Associated objects and embedded objects can be indexed as part of the root entity index. This allows searches of an entity based on properties of associated objects.
14.3.1. Indexing Associated Objects
The aim of the following example is to return places where the associated city is Atlanta via the Lucene query
address.city:Atlanta
. The place fields are indexed in the Place
index. The Place
index documents also contain the following fields:
address.street
address.city
These fields are also able to be queried.
Example 14.4. Indexing associations
@Indexed public class Place { @Field private String name; @IndexedEmbedded @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.REMOVE}) private Address address; } public class Address { @Field private String street; @Field private String city; @ContainedIn @OneToMany(mappedBy = "address") private Set<Place> places; }
14.3.2. @IndexedEmbedded
When using the
@IndexedEmbedded
technique, data is denormalized in the Lucene index. As a result, the Lucene-based Query API must be updated with any changes in the Place
and Address
objects to keep the index up to date. Ensure the Place
Lucene document is updated when its Address
changes by marking the other side of the bidirectional relationship with @ContainedIn
. @ContainedIn
can be used for both associations pointing to entities and on embedded objects.
The
@IndexedEmbedded
annotation can be nested. Attributes can be annotated with @IndexedEmbedded
. The attributes of the associated class are then added to the main entity index. In the following example, the index will contain the following fields:
- name
- address.street
- address.city
- address.ownedBy_name
Example 14.5. Nested usage of @IndexedEmbedded
and @ContainedIn
@Indexed public class Place { @Field private String name; @IndexedEmbedded @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.REMOVE}) private Address address; } public class Address { @Field private String street; @Field private String city; @IndexedEmbedded(depth = 1, prefix = "ownedBy_") private Owner ownedBy; @ContainedIn @OneToMany(mappedBy = "address") private Set<Place> places; } public class Owner { @Field private String name; }
The default prefix is
propertyName
, following the traditional object navigation convention. This can be overridden using the prefix attribute as it is shown on the ownedBy
property.
Note
The prefix cannot be set to the empty string.
The
depth
property is used when the object graph contains a cyclic dependency of classes. For example, if Owner
points to Place
. the Query Module stops including attributes after reaching the expected depth, or object graph boundaries. A self-referential class is an example of cyclic dependency. In the provided example, because depth is set to 1, any @IndexedEmbedded
attribute in Owner
is ignored.
Using
@IndexedEmbedded
for object associations allows queries to be expressed using Lucene's query syntax. For example:
- Return places where name contains JBoss and where address city is Atlanta. In Lucene query this is:
+name:jboss +address.city:atlanta
- Return places where name contains JBoss and where owner's name contain Joe. In Lucene query this is:
+name:jboss +address.ownedBy_name:joe
This operation is similar to the relational join operation, without data duplication. Out of the box, Lucene indexes have no notion of association; the join operation does not exist. It may be beneficial to maintain the normalized relational model while benefiting from the full text index speed and feature richness.
An associated object can be also be
@Indexed
. When @IndexedEmbedded
points to an entity, the association must be directional and the other side must be annotated using @ContainedIn
. If not, the Lucene-based Query API cannot update the root index when the associated entity is updated. In the provided example, a Place
index document is updated when the associated Address instance updates.
14.3.3. The targetElement Property
It is possible to override the object type targeted using the
targetElement
parameter. This method can be used when the object type annotated by @IndexedEmbedded
is not the object type targeted by the data grid and the Lucene-based Query API. This occurs when interfaces are used instead of their implementation.
Example 14.6. Using the targetElement
property of @IndexedEmbedded
@Indexed public class Address { @Field private String street; @IndexedEmbedded(depth = 1, prefix = "ownedBy_", targetElement = Owner.class) private Person ownedBy; ... } public class Owner implements Person { ... }