13.2. Mapper des entités dans la structure de l'index


13.2.1. Mappage d'une entité

Toutes les informations de métadonnées requises pour indexer des entités est décrite dans des annotations. Les fichiers de mappage XML sont donc inutiles. Vous pouvez toujours utiliser les fichiers de mappage Hibernate pour une configuration Hibernate de base, mais la configuration spécifique à Hibernate Search doit être indiquée par des annotations.

13.2.1.1. Mappage de base

Commençons par les annotations les plus couramment utilisées pour mapper une entité.
L'API de requête basée sur Lucene utilise les annotations courantes suivantes pour mapper les entitées :
  • @Indexed
  • @Field
  • @NumericField
  • @Id
13.2.1.1.1. @Indexed
Il convient en premier lieu de déclarer une classe persistente comme indexable, en annotant la classe avec @Indexed (toutes les entités non annotées avec @Indexed seront ignorées par le processus d'indexation) :

Exemple 13.8. Rendre une classe indexable avec @Indexed

@Entity
@Indexed
public class Essay {
    ...
}
Copy to Clipboard Toggle word wrap
Vous pouvez également spécifier l'attribut index de l'annotation @Indexed pour changer le nom par défaut de l'index.
13.2.1.1.2. @Field
Pour chaque propriété (ou attribut) de votre entité, vous avez la possibilité de décrire comment elle sera indexée. La valeur par défaut (aucune annotation présente) signifie que la propriété est ignorée par le procédé d'indexation. @Field déclare une propriété comme indexée et autorise la configuration de plusieurs aspects du procédé d'indexation en définissant un ou plusieurs des attributs suivants :
  • name : décrit le nom sous lequel la propriété doit être stockée dans le Document Lucene. La valeur par défaut est le nom de propriété (d'après la convention de JavaBeans)
  • store : indique si la propriété est stockée dans l'index Lucene. Vous pouvez stocker la valeur Store.YES (utilise plus d'espace dans l'index mais permet une projection, voir Section 13.3.1.10.5, « Projection »), la stocker de manière compressée Store.COMPRESS (utilise davantage de CPU), ou refuser tout stockage Store.NO (valeur par défaut). Lorsqu'une propriété est stockée, vous pouvez récupérer sa valeur initiale à partir du document Lucene, indépendamment de l'indexation de l'élément.
  • index: décrit si la propriété est indexée. Les valeurs disponibles sont Index.NO (aucune indexation, la propriété ne peut être trouvée par une requête), Index.YES (l'élement est indexé et peut être recherché). La valeur par défaut est Index.YES. Index.NO peut être utile lorsqu'une propriété n'a pas l'obligation d'être trouvable mais doit être disponible pour une projection.

    Note

    Index.NO en association avec Analyze.YES ou Norms.YES est inutile car analyze et norms requièrent que la propriété soit indexée.
  • analyze : détermine si la propriété est analysée (Analyze.YES) ou pas (Analyze.NO). La valeur par défaut est Analyze.YES.

    Note

    Que vous souhaitiez ou non analyser une propriété dépendra de si vous souhaitez rechercher l'élément tel quel, ou par les mots qu'il contient. Il est utile d'analyser un champ de texte, mais peut-être pas un champ de date.

    Note

    Les champs utilisés pour trier ne doivent pas être analysés.
  • norms : décrit si l'information concernant le boosting de temps d'indexation doit être stocké (Norms.YES) ou pas (Norms.NO). Ne pas la stocker peut économiser une large partie de la mémoire, mais aucune information concernant le boosting de temps d'indexation ne sera disponible. La valeur par défaut est Norms.YES.
  • termVector : décrit des collections de paires de fréquence d'un terme. Cet attribut permet de stocker les vecteurs de terme dans les documents au cours de l'indexation. La valeur par défaut est TermVector.NO.
    Les différentes valeurs de cet attribut sont :
    Expand
    Valeur Définition
    TermVector.YES Stocke les vecteurs de terme de chaque document et crée deux matrices synchronisées, une contenant des termes de document et l'autre contenant la fréquence d'un terme.
    TermVector.NO Ne pas stocker de vecteurs de terme.
    TermVector.WITH_OFFSETS Stocke le vecteur de terme et les informations de décalage de jeton. Identique à TermVector.YES mais contient également les informations de position de décalage de démarrage et de fin pour les termes.
    TermVector.WITH_POSITIONS Stocke le vecteur de terme et les informations de position de jeton. Identique à TermVector.YES mais contient également les positions ordinales de chaque occurrence d'un terme dans un document.
    TermVector.WITH_POSITION_OFFSETS Stocke le vecteur de terme, la position du jeton et les informations de décalage. Un mélange de YES, WITH_OFFSETS et WITH_POSITIONS.
  • indexNullAs : par défaut, les valeurs nulles sont ignorées et pas indexées. Cependant, l'utilisation de indexNullAs vous permet de spécifier une chaîne qui sera insérée comme jeton pour la valeur null. Cette valeur est définie par défaut comme Field.DO_NOT_INDEX_NULL, indiquant que les valeurs null ne devraient pas être indexées. Vous pouvez définir cette valeur comme Field.DEFAULT_NULL_TOKEN pour indiquer qu'un jeton null par défaut doit être utilisé. Ce jeton null par défaut peut être spécifié dans la configuration en utilisant hibernate.search.default_null_token. Si cette propriété n'est pas définie et que vous spécifiez Field.DEFAULT_NULL_TOKEN, la chaîne "_null_" sera utilisée par défaut.

    Note

    Lorsque le paramètre indexNullAs est utilisé, il est important d'utiliser le même jeton dans la requête de recherche pour rechercher les valeurs null. Il est également recommandé d'utiliser cette fonctionnalité uniquement avec des champs non-analysés (analyze=Analyze.NO).

    Avertissement

    Lors de l'implémentation d'une classe FieldBridge ou TwoWayFieldBridge personnalisée, il appartient au développeur de gérer l'indexation des valeurs nulles (voir JavaDocs de LuceneOptions.indexNullAs()).
13.2.1.1.3. @NumericField
Il existe une annotation associée à @Field appelée @NumericField pouvant être spécifiée dans la même étendue que @Field ou @DocumentId. Elle peut être spécifiée pour les propriétés Integer, Long, Float et Double. Au moment de l'indexation, la valeur sera indexée à l'aide d'une structure Trie. Une propriété indexée comme champ numérique plutôt que propriété standard permet une requête de données plus rapide que sur des propriétés @Field standards. L'annotation @NumericField accepte les paramètres suivants :
Expand
Valeur Définition
forField (Facultatif) Spécifier le nom du @Field correspondant qui sera indexé comme numérique. Cela est uniquement obligatoire lorsque la propriété contient plus d'une déclaration @Field.
precisionStep (Facultatif) Change la manière dont la structure « Trie » est stockée dans l'index. Des étapes de précision plus petites mènent à davantage d'utilisation d'espace disque et des requêtes de trie et de gamme plus rapides. Des valeurs supérieures conduisent à un espace utilisé moindre et à une performance de requête de gamme plus proche de la requête de gamme dans les @Fields normaux. La valeur par défaut est 4.
@NumericField prend uniquement en charge Double, Long, Integer et Float. Il n'est pas possible de prendre un avantage d'une fonctionnalité similaire dans Lucene pour les autres types numériques. Les types restants doivent donc utiliser la chaîne dont le codage est effectué par la classe TwoWayFieldBridge par défaut ou personnalisée.
Il est possible d'utiliser une classe NumericFieldBridge personnalisée si vous savez gérer l'approximation lors de la transformation du type :

Exemple 13.9. Définition d'une classe NumericFieldBridge personnalisée

public class BigDecimalNumericFieldBridge extends NumericFieldBridge {
   private static final BigDecimal storeFactor = BigDecimal.valueOf(100);

   @Override
   public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
      if ( value != null ) {
         BigDecimal decimalValue = (BigDecimal) value;
         Long indexedValue = Long.valueOf( decimalValue.multiply( storeFactor ).longValue() );
         luceneOptions.addNumericFieldToDocument( name, indexedValue, document );
      }
   }

    @Override
    public Object get(String name, Document document) {
        String fromLucene = document.get( name );
        BigDecimal storedBigDecimal = new BigDecimal( fromLucene );
        return storedBigDecimal.divide( storeFactor );
    }

}
Copy to Clipboard Toggle word wrap
13.2.1.1.4. @Id
Pour finir, la propriété id (identifiant) d'une entité est une propriété spéciale utilisée par Hibernate Search pour assurer l'unicité d'index d'une entité donnée. Un id doit être stocké et ne pas être segmenté. Pour identifier une propriété comme identifiant d'index, utilisez l'annotation @DocumentId. Si vous utilisez JPA et que vous avez spécifié @Id, vous pouvez omettre @DocumentId. L'identifiant d'entité choisi sera également utilisé comme identifiant de document.

Exemple 13.10. Spécification des propriétés indexées

@Entity
@Indexed
public class Essay {
    ...

    @Id
    @DocumentId
    public Long getId() { return id; }

    @Field(name="Abstract", store=Store.YES)
    public String getSummary() { return summary; }

    @Lob
    @Field
    public String getText() { return text; }

    @Field @NumericField( precisionStep = 6)
    public float getGrade() { return grade; }
}
Copy to Clipboard Toggle word wrap
Exemple 13.10, « Spécification des propriétés indexées » définit un index avec quatre champs : id , Abstract, text et grade. Veuillez noter que par défaut, le nom de champ ne contient pas de majuscules, d'après les normes JavaBean. Le champ grade est annoté comme numérique avec une étape de précision légèrement plus large que celle par défaut.

13.2.1.2. Mapper des propriétés plusieurs fois

Il vous faut parfois mapper une propriété plusieurs fois par index, avec des stratégies d'indexation légèrement différentes. Par exemple, le triage d'une requête par champ nécessite que le champ soit désanalysé. Pour la trier et la rechercher par mots, cette propriété doit être indexée, une fois analysée et une fois désanalysée. @Fields vous permet d'y parvenir.

Exemple 13.11. Utilisation de @Fields pour mapper une propriété plusieurs fois

@Entity
@Indexed(index = "Book" )
public class Book {
    @Fields( {
            @Field,
            @Field(name = "summary_forSort", analyze = Analyze.NO, store = Store.YES)
            } )
    public String getSummary() {
        return summary;
    }

    ...
}
Copy to Clipboard Toggle word wrap
Dans cet exemple, le champ summary est indexé deux fois, la première fois comme summary avec des jetons, puis comme summary_forSort sans jetons.

13.2.1.3. Objets associés et intégrés

Les objets associés ainsi que les objets intégrés peuvent être indexés dans l'index d'entité de root. Cela peut être utile si vous comptez rechercher une entité donnée selon les propriétés d'objets associés. Dans Exemple 13.12, « Associations d'indexation », le but est de retourner les lieux où la ville associée est Atlanta (en language d'analyseur de requête Lucene, cela se traduirait par address.city:Atlanta). Les champs de lieu seront indexés dans l'index Place. Les documents de l'index Place contiendront également les champs address.id, address.street et address.city pour lesquels vous pourrez faire une requête.

Exemple 13.12. Associations d'indexation

@Entity
@Indexed
public class Place {
    @Id
    @GeneratedValue
    @DocumentId
    private Long id;

    @Field
    private String name;

    @OneToOne( cascade = { CascadeType.PERSIST, CascadeType.REMOVE } )
    @IndexedEmbedded
    private Address address;
    ....
}

@Entity
public class Address {
    @Id
    @GeneratedValue
    private Long id;

    @Field
    private String street;

    @Field
    private String city;

    @ContainedIn
    @OneToMany(mappedBy="address")
    private Set<Place> places;
    ...
}
Copy to Clipboard Toggle word wrap
Les données étant dénormalisées dans l'index Lucene avec l'utilisation de la technique @IndexedEmbedded, Hibernate Search doit être informé de tout changement dans les objets Place et Address afin de maintenir l'index à jour. Pour garantir que le document Lucene Place est mis à jour lorsque son Address est modifiée, indiquez @ContainedIn de l'autre côté de la relation bidirectionnelle.

Note

@ContainedIn est utile pour les deux associations indiquant les entités ainsi que pour les (collections d') objets intégrés.
Voici un exemple démontrant l'imbrication de @IndexedEmbedded.

Exemple 13.13. Utilisation imbriquée de @IndexedEmbedded et @ContainedIn

@Entity
@Indexed
public class Place {
    @Id
    @GeneratedValue
    @DocumentId
    private Long id;

    @Field
    private String name;

    @OneToOne( cascade = { CascadeType.PERSIST, CascadeType.REMOVE } )
    @IndexedEmbedded
    private Address address;
    ....
}

@Entity
public class Address {
    @Id
    @GeneratedValue
    private Long id;

    @Field
    private String street;

    @Field
    private String city;

    @IndexedEmbedded(depth = 1, prefix = "ownedBy_")
    private Owner ownedBy;

    @ContainedIn
    @OneToMany(mappedBy="address")
    private Set<Place> places;
    ...
}

@Embeddable
public class Owner {
    @Field
    private String name;
   ...
}
Copy to Clipboard Toggle word wrap
Chaque attribut @*ToMany, @*ToOne et @Embedded peut être annoté par @IndexedEmbedded. Les attributs de la classse associée seront ensuite ajoutés à l'index d'entités principal. Dans l'Exemple 13.13, « Utilisation imbriquée de @IndexedEmbedded et @ContainedIn », l'index contiendra les champs suivants :
  • id
  • name
  • address.street
  • address.city
  • address.ownedBy_name
Le préfixe par défaut est propertyName., suivi de la convention de navigation d'objet traditionnelle. Vous pouvez le remplacer à l'aide de l'attribut prefix tel que démontré dans la propriété ownedBy.

Note

Le préfixe ne peut être défini sur la chaîne vide.
La propriété depth est nécessaire lorsque le graphique de l'objet comprend une dépendance cyclique de classes (non d'instances). Par exemple, si Owner indique Place. Hibernate Search arrêtera d'inclure les attributs intégrés indexés une fois la profondeur souhaitée atteinte (ou dès que les limites du graphique de l'objet sont atteintes). Une classe possédant une auto-référence est un exemple de dépendance cyclique. Dans notre exemple, depth étant défini sur 1, tout attribut @IndexedEmbedded dans Owner (le cas échéant) sera ignoré.
L'utilisation de @IndexedEmbedded pour les associations d'objets vous permet d'exprimer des requêtes (à l'aide de la syntaxe de requête de Lucene) telles que :
  • Renvoyer des lieux dont le nom contient JBoss et dont l'adresse se trouve à Atlanta. En requête Lucene, cela s'apparente à :
    +name:jboss +address.city:atlanta
    Copy to Clipboard Toggle word wrap
  • Renvoyer des lieux dont le nom contient JBoss et dont le nom du propriétaire contient Joe. En requête Lucene, cela s'apparente à :
    +name:jboss +address.ownedBy_name:joe
    Copy to Clipboard Toggle word wrap
Ce comportement imite l'opération de raccord relationnel de manière plus efficace (au prix de duplication de données). Veuillez noter que les index Lucene n'ont aucune notion d'association, l'opération de raccord n'existe pas. Il peut être utile de maintenir le modèle relationnel normalisé tout en bénéficiant de la rapidité de l'index de texte intégral et de la richesse de la fonctionnalité.

Note

Un objet associé peut (sans obligation) être @Indexed
Lorsque @IndexedEmbedded indique une entitié, l'association doit être directionnelle et l'autre côté doit être annoté @ContainedIn (tel que dans l'exemple précédent). Sinon, Hibernate Search n'a aucun moyen de mettre à jour l'index de root quand l'entité associée est mise à jour (dans notre exemple, un document index Place doit être mis à jour lorsque l'instance Address associée est mise à jour).
Parfois, le type d'objet annoté par @IndexedEmbedded n'est pas le type d'objet ciblé par Hibernate et Hibernate Search. Cela est particulièrement le cas lorsque les interfaces sont utilisées au lieu de leur implémentation. Vous pouvez pour cette raison remplacer le type d'objet ciblé par Hibernate Search à l'aide du paramètre targetElement.

Exemple 13.14. Utilisation de la propriété targetElement de @IndexedEmbedded

@Entity
@Indexed
public class Address {
    @Id
    @GeneratedValue
    @DocumentId
    private Long id;

    @Field
    private String street;

    @IndexedEmbedded(depth = 1, prefix = "ownedBy_", targetElement = Owner.class)
    @Target(Owner.class)
    private Person ownedBy;


    ...
}

@Embeddable
public class Owner implements Person { ... }
Copy to Clipboard Toggle word wrap
L'annotation @IndexedEmbedded fournit également un attribut includePaths pouvant être utilisé comme alternative à ou en association avec la méthode depth (profondeur).
Lorsque seulement depth est utilisé, tous les champs indexés du type intégré seront ajoutés de manière récursive à la même profondeur. Cela complique la sélection d'un seul chemin spécifique sans ajouter tous les autres champs, qui ne sont pas forcément utiles.
Pour éviter de charger et d'indexer des entités non nécessaires, vous pouvez spécifier les chemins dont vous avez besoin. Une application typique peut nécessiter plusieurs profondeurs pour différents chemins, c'est-à-dire des chemins spécifiés explicitement, tel que démontré dans Exemple 13.15, « Utilisation de la propriété includePaths de @IndexedEmbedded ».

Exemple 13.15. Utilisation de la propriété includePaths de @IndexedEmbedded

@Entity
@Indexed
public class Person {

   @Id
   public int getId() {
      return id;
   }

   @Field
   public String getName() {
      return name;
   }

   @Field
   public String getSurname() {
      return surname;
   }

   @OneToMany
   @IndexedEmbedded(includePaths = { "name" })
   public Set<Person> getParents() {
      return parents;
   }

   @ContainedIn
   @ManyToOne
   public Human getChild() {
      return child;
   }

    ...//other fields omitted
Copy to Clipboard Toggle word wrap
L'utilisation d'un mappage comme dans Exemple 13.15, « Utilisation de la propriété includePaths de @IndexedEmbedded » vous permet de rechercher une classe Person par name (prénom) et/ou surname (nom de famille), et/ou le name d'un parent. Le surname du parent ne sera pas indexé, la recherche des noms de famille d'un parent sera donc impossible mais cela accélèrera l'indexation, économisera de l'espace et améliorera la performance générale.
La méthode includePaths de @IndexedEmbedded incluera les chemins spécifiés en plus de que vous indexeriez normalement en spécifiant une valeur limitée pour depth. Lorsque vous utilisez includePaths et laissez depth indéfini, le comportement est équivalent à une configuration depth=0 : seuls les chemins inclus sont indexés.

Exemple 13.16. Utilisation de la propriété includePaths de @IndexedEmbedded

@Entity
@Indexed
public class Human {

   @Id
   public int getId() {
      return id;
   }

   @Field
   public String getName() {
      return name;
   }

   @Field
   public String getSurname() {
      return surname;
   }

   @OneToMany
   @IndexedEmbedded(depth = 2, includePaths = { "parents.parents.name" })
   public Set<Human> getParents() {
      return parents;
   }

   @ContainedIn
   @ManyToOne
   public Human getChild() {
      return child;
   }

    ...//other fields omitted
Copy to Clipboard Toggle word wrap
Dans Exemple 13.16, « Utilisation de la propriété includePaths de @IndexedEmbedded », chaque humain aura les attributs de son nom et nom de famille indexés. Le nom et nom de famille des parents sera également indexé, de manière récursive jusqu'à la seconde ligne à cause de l'attribut de la méthode depth. Il sera possible de rechercher par nom et nom de famille, de la personne directement, de ses parents ou grands-parents. Au-delà du second niveau, nous indexerons également un niveau supplémentaire mais uniquement le nom, pas le nom de famille.
Cela implique les champs suivants dans l'index :
  • id : comme clé primaire
  • _hibernate_class : stocke le type d'entité
  • name : comme champ direct
  • surname : comme champ direct
  • parents.name : comme champ intégré à profondeur 1
  • parents.surname : comme champ intégré à profondeur 1
  • parents.parents.name : comme champ intégré à profondeur 2
  • parents.parents.surname : comme champ intégré à profondeur 2
  • parents.parents.parents.name : comme chemin supplémentaire tel que spécifié par includePaths. Le premier parents. est déduit à partir du nom de champ, le reste du chemin est l'attribut de includePaths
Le fait d'exercer un contrôle explicite des chemins indexés peut être plus facile si vous concevez votre application en définissant d'abord les requêtes nécessaires car vous connaîtrez exactement les champs vous aurez besoin et ceux inutiles à la mise en œuvre de votre cas d'utilisation.
Retour au début
Red Hat logoGithubredditYoutubeTwitter

Apprendre

Essayez, achetez et vendez

Communautés

À propos de la documentation Red Hat

Nous aidons les utilisateurs de Red Hat à innover et à atteindre leurs objectifs grâce à nos produits et services avec un contenu auquel ils peuvent faire confiance. Découvrez nos récentes mises à jour.

Rendre l’open source plus inclusif

Red Hat s'engage à remplacer le langage problématique dans notre code, notre documentation et nos propriétés Web. Pour plus de détails, consultez le Blog Red Hat.

À propos de Red Hat

Nous proposons des solutions renforcées qui facilitent le travail des entreprises sur plusieurs plates-formes et environnements, du centre de données central à la périphérie du réseau.

Theme

© 2025 Red Hat