Как сопоставить вычисленные свойства с помощью JPA и Hibernate

104

У моего Java-бина есть свойство childCount. Это свойство не сопоставляется со столбцом базы данных . Вместо этого он должен быть вычислен базой данных с помощью COUNT()функции, работающей на объединении моего Java-компонента и его дочерних элементов. Было бы еще лучше, если бы это свойство можно было рассчитывать по запросу / «лениво», но это не обязательно.

В худшем случае я могу установить свойство этого bean-компонента с помощью HQL или Criteria API, но я бы предпочел этого не делать.

@FormulaАннотации Hibernate могут помочь, но я почти не нашел документации.

Любая помощь очень ценится. Спасибо.

Франсуа
источник

Ответы:

162

JPA не предлагает никакой поддержки производного свойства, поэтому вам придется использовать расширение для конкретного поставщика. Как вы упомянули, @Formulaидеально подходит для этого при использовании Hibernate. Вы можете использовать фрагмент 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 - производительность и переносимость .

Без дополнительной информации я не могу дать более точный ответ, но ссылка выше должна быть полезной.

Смотрите также:

Паскаль Тивент
источник
Спасибо, Паскаль. Вы знаете, почему следующее не работает? @Formula (value = "(выберите count ( ) из внутреннего соединения ic c, где ic.category_id = c.id и c.id = id)") public Integer getCountInternal () {return countInternal; } Запрос в порядке, и я вижу, что он выполняется в журналах. Сопоставление кажется нормальным: main org.hibernate.cfg.Ejb3Column - формула привязки (select count ( ) бла-бла-бла Но countInternal остается установленным на свое начальное значение (-1) :( Я пробовал использовать аннотации полей вместо аннотаций геттеров, но он по-прежнему не работает.
Франсуа
Спасибо, это очень помогло мне с моей задачей!
Mythul
13
@NeilMcGuigan: В @Formulaаннотации используется SQL, а не HQL.
user1438038 09
7
Только что узнал, насколько важно заключать запрос в круглые скобки. Всегда получалось исключение SQL, поскольку этот запрос, конечно, становится подзапросом при загрузке объекта хоста. MySQL не любил встроенный выбор без круглых скобок.
sorrymissjackson
1
Новая ссылка на статью: blog.eyallupu.com/2009/07/hibernate-dehibited-properties.html
Аднан
53

Как объясняется в этой статье , у вас есть три варианта:

  • либо вы вычисляете атрибут с помощью @Transientметода
  • вы также можете использовать @PostLoadпрослушиватель сущностей
  • или вы можете использовать специальную @Formulaаннотацию Hibernate

Хотя Hibernate позволяет использовать @Formula , с JPA вы можете использовать обратный вызов @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;
}

Для более сложных запросов вы можете использовать Hibernate @Formula, как описано в этой статье :

@Formula(
    "round(" +
    "   (interestRate::numeric / 100) * " +
    "   cents * " +
    "   date_part('month', age(now(), createdOn)" +
    ") " +
    "/ 12) " +
    "/ 100::numeric")
private double interestDollars;
Влад Михалча
источник
7
Однако это не позволяет использовать более сложные запросы
Хуберт Гжесковяк,
2
Я обновил ответ, так что теперь у вас есть решение и для более сложных запросов.
Влад Михалча
1

Взгляните на Blaze-Persistence Entity Views, который работает поверх JPA и обеспечивает первоклассную поддержку DTO. Вы можете спроецировать что угодно на атрибуты в Entity Views, и даже если это возможно, существующие узлы соединения будут повторно использоваться для ассоциаций.

Вот пример отображения

@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)

вопрос, который я задал, заключался в том, есть ли способ получить значения столбца Calculated с помощью JPA / Hibernate с mySQL?

Что я делаю не так ?

bob269
источник