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

104

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, il devrait être calculé par la base de données avec une COUNT()fonction opérant sur la jointure de mon bean Java et de ses enfants. Ce serait encore mieux si cette propriété pouvait être calculée à la demande / "paresseusement", 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' @Formulaannotation Hibernate peut aider, mais je pouvais à peine trouver de la documentation.

Toute aide grandement appréciée. Merci.

François
la source

Réponses:

162

JPA n'offre aucune prise en charge pour les propriétés dérivées, vous devrez donc utiliser une extension spécifique au fournisseur. Comme vous l'avez mentionné, @Formulaest parfait pour cela lorsque vous utilisez 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;

idest le idde l'entité actuelle.

Le billet de blog suivant vaut la peine d'être lu: Propriétés dérivées d'Hibernate - Performances 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 également:

Pascal Thivent
la source
Merci Pascal. Avez-vous une idée de la raison pour laquelle ce qui suit ne fonctionne pas? @Formula (value = "(sélectionnez count ( ) à partir de la jointure interne ic c où ic.category_id = c.id et c.id = id)") public Integer getCountInternal () {return countInternal; } La requête est OK et je la vois s'exécuter dans les journaux. Le mappage semble OK: main org.hibernate.cfg.Ejb3Column - formule de liaison (select count ( ) blah blah blah Mais countInternal reste défini sur sa valeur initiale (-1) :( J'ai essayé d'utiliser des annotations de champ au lieu d'annotations getter, mais ça ne marche toujours pas.
Francois
Merci, cela m'a vraiment beaucoup aidé dans ma tâche!
Mythul
13
@NeilMcGuigan: L' @Formulaannotation utilise SQL, pas HQL.
user1438038
7
Je viens d'apprendre à quel point il est important d'avoir les parenthèses autour de la requête. Toujours fini avec une exception SQL, puisque cette requête devient bien sûr une sous-requête lorsque l'objet hôte est chargé. MySQL n'aimait pas la sélection en ligne sans parenthèses.
sorrymissjackson
1
Le nouveau lien vers l'article est: blog.eyallupu.com/2009/07/hibernate-derived-properties.html
Adnan
53

Comme expliqué dans cet article , vous avez trois options:

  • soit vous calculez l'attribut à l'aide d'une @Transientméthode
  • vous pouvez également utiliser l' @PostLoadécouteur d'entité
  • ou vous pouvez utiliser l' @Formulaannotation spécifique Hibernate

Alors qu'Hibernate vous permet d'utiliser @Formula , avec JPA, vous pouvez utiliser le rappel @PostLoad pour remplir une propriété transitoire 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;
}

Pour les requêtes plus complexes, vous pouvez utiliser Hibernate @Formula, comme expliqué dans cet article :

@Formula(
    "round(" +
    "   (interestRate::numeric / 100) * " +
    "   cents * " +
    "   date_part('month', age(now(), createdOn)" +
    ") " +
    "/ 12) " +
    "/ 100::numeric")
private double interestDollars;
Vlad Mihalcea
la source
7
Cela ne permet cependant pas des requêtes plus complexes
Hubert Grzeskowiak
2
J'ai mis à jour la réponse, vous avez donc maintenant une solution pour les requêtes plus complexes.
Vlad Mihalcea
1

Jetez un œil aux vues des entités Blaze-Persistence qui fonctionne au-dessus de JPA et fournit un support DTO de première classe. Vous pouvez projeter n'importe quoi sur des attributs dans Entity Views et il réutilisera même les nœuds de jointure existants pour les associations si possible.

Voici un exemple de cartographie

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

Récupérer ceci générera une requête JPQL / HQL similaire à celle-ci

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êtes personnalisées qui pourraient également vous intéresser: https://blazebit.com/blog/2017/entity-view-mapping-subqueries.html

Christian Beikov
la source
0

j'ai essayé ceci sur mon code

 @Formula(value = "(select DATEDIFF(expiry_date,issue_date)  from document_storage)")
    private String  myColumn;

les erreurs que je reçois lorsque je cours et affiche ma vue avant même d'essayer d'afficher la colonne sur la moustache est quelque chose comme ça

java.lang.NullPointerException
    at java.base/java.lang.String$CaseInsensitiveComparator.compare(String.java:1241)
    at java.base/java.lang.String$CaseInsensitiveComparator.compare(String.java:1235)
    at java.base/java.util.TreeMap.getEntryUsingComparator(TreeMap.java:374)
    at java.base/java.util.TreeMap.getEntry(TreeMap.java:343)
    at java.base/java.util.TreeMap.get(TreeMap.java:277)
    at com.mysql.cj.result.DefaultColumnDefinition.findColumn(DefaultColumnDefinition.java:182)

la question que j'ai posée était : y a-t-il un moyen d'obtenir les valeurs de la colonne calculée en utilisant JPA / Hibernate avec mySQL?

Qu'est-ce que je fais mal ?

bob269
la source