135 votes

Comment mapper les propriétés calculées avec JPA et Hibernate

Mon bean Java a une propriété childCount. Cette propriété n'est pas mappée à une colonne de base de données. Au lieu de cela, elle devrait être calculée par la base de données avec une fonction COUNT() opérant sur la jointure de mon bean Java et ses enfants. Ce serait encore mieux si cette propriété pouvait être calculée à la demande / "de manière paresseuse", mais ce n'est pas obligatoire.

Dans le pire des cas, je peux définir la propriété de ce bean avec HQL ou l'API Criteria, mais je préférerais ne pas le faire.

L'annotation Hibernate @Formula pourrait aider, mais j'ai du mal à trouver de la documentation.

Toute aide serait grandement appréciée. Merci.

206voto

Pascal Thivent Points 295221

JPA n'offre aucun support pour les propriétés dérivées, vous devrez donc utiliser une extension spécifique au fournisseur. Comme vous l'avez mentionné, @Formula est parfait pour cela lors de l'utilisation de Hibernate. Vous pouvez utiliser un fragment SQL :

@Formula("PRICE*1.155")
private float finalPrice;

Ou même des requêtes complexes sur d'autres tables :

@Formula("(select min(o.creation_date) from Orders o where o.customer_id = id)")
private Date firstOrderDate;

id est l'id de l'entité actuelle.

L'article de blog suivant vaut la lecture : Propriétés dérivées Hibernate - Performance et Portabilité.

Sans plus de détails, je ne peux pas donner de réponse plus précise mais le lien ci-dessus devrait être utile.

Voir aussi :

0 votes

Merci Pascal. Avez-vous une idée pourquoi le code suivant ne fonctionne pas? @Formula(value = "(select count() from ic inner join c where ic.category_id = c.id and c.id = id)") public Integer getCountInternal() { return countInternal; } La requête est correcte et je la vois exécutée dans les journaux. Le mapping semble correct: main org.hibernate.cfg.Ejb3Column - binding formula (select count() blah blah blah Mais countInternal reste fixé à sa valeur initiale (-1) :( J'ai essayé d'utiliser des annotations de champ plutôt que des annotations de getter, mais cela ne fonctionne toujours pas.

1 votes

Tu es l'homme Pascal. Est-ce que le SQL dans la formule est du vrai SQL ou du HQL ? Merci

21 votes

@NeilMcGuigan : L'annotation @Formula utilise SQL, pas HQL.

78voto

Vlad Mihalcea Points 3628

Vous avez trois options :

  • soit vous calculez l'attribut en utilisant une méthode @Transient
  • vous pouvez également utiliser le listener d'entité @PostLoad
  • ou vous pouvez utiliser l'annotation spécifique à Hibernate @Formula

Alors qu'Hibernate vous permet d'utiliser @Formula, avec JPA, vous pouvez utiliser le callback @PostLoad pour remplir une propriété transient avec le résultat d'un calcul :

@Column(name = "price")
private Double price;

@Column(name = "tax_percentage")
private Double taxes;

@Transient
private Double priceWithTaxes;

@PostLoad
private void onLoad() {
    this.priceWithTaxes = price * taxes;
}

Donc, vous pouvez utiliser le @Formula de Hibernate de cette manière :

@Formula("""
    round(
       (interestRate::numeric / 100) *
       cents *
       date_part('month', age(now(), createdOn)
    )
    / 12)
    / 100::numeric
    """)
private double interestDollars;

9 votes

Cependant, cela ne permet pas de réaliser des requêtes plus complexes

2 votes

J'ai mis à jour la réponse donc maintenant vous avez une solution pour les requêtes plus complexes aussi.

1 votes

Est-ce que je peux utiliser order by avec des champs transitoires qui sont définis par @PostLoad ?

3voto

Christian Beikov Points 223

Jetez un œil à Blaze-Persistence Entity Views qui fonctionne sur JPA et offre un support DTO de premier ordre. Vous pouvez projeter n'importe quoi en attributs au sein des Entity Views et il réutilisera même les nœuds de jointure existants pour les associations si possible.

Voici un exemple de mapping

@EntityView(Order.class)
interface OrderSummary {
  Integer getId();
  @Mapping("SUM(orderPositions.price * orderPositions.amount * orderPositions.tax)")
  BigDecimal getOrderAmount();
  @Mapping("COUNT(orderPositions)")
  Long getItemCount();
}

Interroger cela générera une requête JPQL/HQL similaire à ceci

SELECT
  o.id,
  SUM(p.price * p.amount * p.tax),
  COUNT(p.id)
FROM
  Order o
LEFT JOIN
  o.orderPositions p
GROUP BY
  o.id

Voici un article de blog sur les fournisseurs de sous-requête personnalisés qui pourrait également vous intéresser : https://blazebit.com/blog/2017/entity-view-mapping-subqueries.html

0voto

rafaelune Points 11

Rejoint fonctionne sur la version 3.0 ou ultérieure.

https://hibernate.atlassian.net/browse/HB-765

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X