Jak mapować obliczone właściwości za pomocą JPA i Hibernate

104

Mój bean Java ma właściwość childCount. Ta właściwość nie jest zamapowana na kolumnę bazy danych . Zamiast tego powinien być obliczany przez bazę danych z COUNT()funkcją działającą na złączeniu mojego beana Java i jego elementów podrzędnych. Byłoby jeszcze lepiej, gdyby ta nieruchomość mogła być obliczana na żądanie / „leniwie”, ale nie jest to obowiązkowe.

W najgorszym przypadku mogę ustawić właściwość tej fasoli za pomocą HQL lub Criteria API, ale wolałbym tego nie robić.

@FormulaAdnotacja Hibernate może pomóc, ale ledwo mogłem znaleźć jakąkolwiek dokumentację.

Każda pomoc mile widziana. Dzięki.

Francois
źródło

Odpowiedzi:

162

JPA nie oferuje żadnego wsparcia dla właściwości pochodnych, więc będziesz musiał użyć rozszerzenia dla konkretnego dostawcy. Jak wspomniałeś, @Formulajest do tego idealny przy korzystaniu z Hibernacji. Możesz użyć fragmentu SQL:

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

Lub nawet złożone zapytania w innych tabelach:

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

Gdzie idjest idbieżąca jednostka.

Warto przeczytać następujący wpis na blogu: Właściwości pochodne hibernacji - wydajność i przenośność .

Bez dalszych szczegółów nie mogę udzielić dokładniejszej odpowiedzi, ale powyższy link powinien być pomocny.

Zobacz też:

Pascal Thivent
źródło
Dzięki Pascal. Czy masz pojęcie, dlaczego poniższe rozwiązania nie działają? @Formula (value = "(select count ( ) from ic internal join c where ic.category_id = c.id and c.id = id)") public Integer getCountInternal () {return countInternal; } Zapytanie jest OK i widzę, że jest uruchamiane w dziennikach. Mapowanie wydaje się OK: main org.hibernate.cfg.Ejb3Column - formuła powiązania (select count ( ) blah blah blah Ale countInternal pozostaje ustawiona na wartość początkową (-1) :( Próbowałem używać adnotacji pól zamiast adnotacji pobierających, ale to nadal nie działa
Francois
Dzięki, to naprawdę bardzo mi pomogło w moim zadaniu!
Mythul
13
@NeilMcGuigan: @FormulaAdnotacja używa języka SQL, a nie HQL.
user1438038
7
Właśnie dowiedziałem się, jak ważne jest umieszczanie nawiasów wokół zapytania. Zawsze kończyło się na wyjątku SQL, ponieważ to zapytanie staje się oczywiście podzapytaniem po załadowaniu obiektu hosta. MySQL nie podobał się inline select bez nawiasów.
sorrymissjackson
53

Jak wyjaśniono w tym artykule , masz trzy opcje:

  • albo obliczasz atrybut za pomocą @Transientmetody
  • możesz także użyć @PostLoadnasłuchiwania encji
  • lub możesz użyć @Formulaadnotacji specyficznej dla Hibernate

Podczas gdy Hibernate umożliwia użycie @Formula , w przypadku JPA można użyć wywołania zwrotnego @PostLoad, aby wypełnić właściwość przejściową wynikiem pewnych obliczeń:

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

W przypadku bardziej złożonych zapytań możesz użyć Hibernacji @Formula, jak wyjaśniono w tym artykule :

@Formula(
    "round(" +
    "   (interestRate::numeric / 100) * " +
    "   cents * " +
    "   date_part('month', age(now(), createdOn)" +
    ") " +
    "/ 12) " +
    "/ 100::numeric")
private double interestDollars;
Vlad Mihalcea
źródło
7
To jednak nie pozwala na bardziej złożone zapytania
Hubert Grześkowiak
2
Zaktualizowałem odpowiedź, więc teraz masz rozwiązanie również dla bardziej złożonych zapytań.
Vlad Mihalcea,
1

Spójrz na widoki jednostek Blaze-Persistence, które działają na szczycie JPA i zapewniają pierwszorzędną obsługę DTO. Możesz rzutować wszystko na atrybuty w widokach encji, a nawet, jeśli to możliwe, ponownie użyje istniejących węzłów łączenia dla skojarzeń.

Oto przykładowe mapowanie

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

Pobranie tego spowoduje wygenerowanie zapytania JPQL / HQL podobnego do tego

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

Oto post na blogu o niestandardowych dostawcach podzapytań, który również może być dla Ciebie interesujący: https://blazebit.com/blog/2017/entity-view-mapping-subqueries.html

Christian Beikov
źródło
0

próbowałem tego na moim kodzie

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

błędy, które otrzymuję, gdy uruchamiam i wyświetlam mój widok, nawet przed próbą wyświetlenia kolumny na wąsach, są podobne do tego

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)

Pytanie, które zadałem, brzmi: czy istnieje sposób, aby uzyskać wartości kolumny obliczonej przy użyciu JPA / Hibernate z mySQL?

Co ja robię źle ?

bob269
źródło