計算されたプロパティをJPAおよびHibernateでマップする方法


104

私のJava BeanにはchildCountプロパティがあります。このプロパティはデータベースの列にマップされていません。代わりに、Java Beanとその子の結合で動作するCOUNT()関数を使用して、データベースで計算する必要があります。このプロパティをオンデマンドで/「遅延」で計算できればさらに良いでしょうが、これは必須ではありません。

最悪のシナリオでは、このBeanのプロパティをHQLまたはCriteria APIで設定できますが、設定しない方がよいでしょう。

Hibernate @Formulaアノテーションは役立つかもしれませんが、ドキュメントをほとんど見つけることができませんでした。

どんな助けも大歓迎です。ありがとう。

回答:


162

JPAは派生プロパティのサポートを提供しないため、プロバイダー固有の拡張機能を使用する必要があります。あなたが述べたように、@FormulaHibernateを使用する場合、これは完璧です。SQLフラグメントを使用できます。

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

または、他のテーブルに対する複雑なクエリ:

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

どこにidあるid現在のエンティティのは。

次のブログ投稿は一読に値します:Hibernate Derived Properties-Performance and Portability

詳細がなければ、私はより正確な答えを出すことはできませんが、上記のリンクは役に立ちます。

以下も参照してください。


Pascal、ありがとう。以下がうまくいかない理由がわかりますか?@Formula(value = "(select count()from ic inner join c where ic.category_id = c.id and c.id = id)")public Integer getCountInternal(){return countInternal; クエリは問題なく、ログで実行されていることがわかります。マッピングは問題ないようです:main org.hibernate.cfg.Ejb3Column-バインディング式(select count()blah blah blahしかし、countInternalは初期値(-1)に設定されたままです:(ゲッターアノテーションの代わりにフィールドアノテーションを使用してみましたが、それでもまだ機能しません
フランソワ

ありがとう、それは私の仕事を本当に助けてくれました!
Mythul 2013

13
@NeilMcGuigan:@FormulaアノテーションはHQLではなくSQLを使用します。
user1438038

7
クエリをかっこで囲むことがいかに重要であるかがわかりました。もちろん、このクエリはホストオブジェクトがロードされるとサブクエリになるため、常にSQL例外が発生します。MySQLは括弧なしのインライン選択を好みませんでした。
sorrymissjackson 2015年

1
この記事への新しいリンクは次のとおり
Adnan

53

この記事で説明したように、3つのオプションがあります。

  • @Transientメソッドを使用して属性を計算している
  • @PostLoadエンティティリスナーを使用することもできます
  • または、Hibernate固有の@Formulaアノテーションを使用できます

HibernateではJPAで@Formulaを使用できますが、@ PostLoadコールバックを使用して、いくつかの計算結果を一時的なプロパティに設定できます。

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

より複雑なクエリの場合は@Formulaこの記事で説明されているようにHibernateを使用できます。

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

7
ただし、これはより複雑なクエリを許可しません
Hubert Grzeskowiak

2
答えを更新したので、より複雑なクエリの解決策も手に入りました。
Vlad Mihalcea

1

JPAの上で動作し、ファーストクラスのDTOサポートを提供するBlaze-Persistenceエンティティビューを見てください。エンティティビュー内の属性には何でも投影でき、可能であれば、関連付けのために既存の結合ノードを再利用します。

これがマッピングの例です

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

これを取得すると、これと同様のJPQL / HQLクエリが生成されます

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

こちらも興味深いサブクエリプロバイダーに関するブログ投稿です。https//blazebit.com/blog/2017/entity-view-mapping-subqueries.html


0

私は自分のコードでこれを試しました

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

口ひげに列を表示しようとする前でも、ビューを実行して表示したときに発生するエラーは次のようなものです

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)

私が尋ねた質問は、mySQLでJPA / Hibernateを使用して計算された列の値を取得する方法はありますか?

私は何を間違っていますか?

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.