Come mappare le proprietà calcolate con JPA e Hibernate


104

Il mio Java bean ha una proprietà childCount. Questa proprietà non è mappata a una colonna del database . Invece, dovrebbe essere calcolato dal database con una COUNT()funzione che opera sull'unione del mio Java bean e dei suoi figli. Sarebbe ancora meglio se questa proprietà potesse essere calcolata su richiesta / "pigramente", ma questo non è obbligatorio.

Nella peggiore delle ipotesi, posso impostare la proprietà di questo bean con HQL o l'API dei criteri, ma preferirei non farlo.

L' @Formulaannotazione Hibernate può aiutare, ma sono riuscito a malapena a trovare alcuna documentazione.

Qualsiasi aiuto molto apprezzato. Grazie.

Risposte:


162

JPA non offre alcun supporto per la proprietà derivata, quindi dovrai utilizzare un'estensione specifica del provider. Come hai detto, @Formulaè perfetto per questo quando usi Hibernate. Puoi usare un frammento SQL:

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

O anche query complesse su altre tabelle:

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

Dove idè il iddell'entità corrente.

Vale la pena leggere il seguente post del blog: Proprietà derivate da ibernazione : prestazioni e portabilità .

Senza ulteriori dettagli, non posso dare una risposta più precisa ma il link sopra dovrebbe essere utile.

Guarda anche:


Grazie Pascal. Hai idea del motivo per cui quanto segue non funziona? @Formula (value = "(seleziona count ( ) da ic inner join c dove ic.category_id = c.id e c.id = id)") public Integer getCountInternal () {return countInternal; } La query è corretta e vedo che viene eseguita nei log. La mappatura sembra OK: main org.hibernate.cfg.Ejb3Column - formula vincolante (select count ( ) blah blah blah Ma countInternal rimane impostato al suo valore iniziale (-1) :( Ho provato a usare annotazioni di campo invece di annotazioni getter, ma che ancora non funziona.
Francois

Grazie, mi ha davvero aiutato molto con il mio compito!
Mythul

13
@ NeilMcGuigan: l' @Formulaannotazione utilizza SQL, non HQL.
user1438038

7
Ho appena imparato quanto sia importante avere le parentesi attorno alla query. Finiva sempre con un'eccezione SQL, poiché questa query diventa ovviamente una sottoquery quando viene caricato l'oggetto host. A MySQL non piaceva la selezione in linea senza parentesi.
sorrymissjackson

1
Il nuovo collegamento all'articolo è: blog.eyallupu.com/2009/07/hibernate-derived-properties.html
Adnan

53

Come spiegato in questo articolo , hai tre opzioni:

  • o stai calcolando l'attributo usando un @Transientmetodo
  • puoi anche usare il @PostLoadlistener di entità
  • oppure puoi usare l' @Formulaannotazione specifica di Hibernate

Sebbene Hibernate ti consenta di utilizzare @Formula , con JPA puoi utilizzare il callback @PostLoad per popolare una proprietà transitoria con il risultato di alcuni calcoli:

@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;
}

Per query più complesse, puoi utilizzare Hibernate @Formula, come spiegato in questo articolo :

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

7
Ciò tuttavia non consente query più complesse
Hubert Grzeskowiak

2
Ho aggiornato la risposta, quindi ora hai una soluzione anche per query più complesse.
Vlad Mihalcea

1

Dai un'occhiata a Blaze-Persistence Entity Views che funziona su JPA e fornisce supporto DTO di prima classe. Puoi proiettare qualsiasi cosa sugli attributi all'interno delle viste entità e, se possibile, riutilizzerà anche i nodi di join esistenti per le associazioni.

Ecco un esempio di mappatura

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

Il recupero di questo genererà una query JPQL / HQL simile a questa

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

Ecco un post sul blog sui provider di subquery personalizzate che potrebbero interessarti anche: https://blazebit.com/blog/2017/entity-view-mapping-subqueries.html


0

l'ho provato sul mio codice

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

gli errori che ottengo quando corro e visualizzo la mia vista anche prima di provare a visualizzare la colonna sui baffi è qualcosa del genere

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 domanda che ho posto era : esiste un modo per ottenere i valori della colonna calcolata utilizzando JPA / Hibernate con mySQL?

Che cosa sto facendo di sbagliato ?

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.