Como mapear propriedades calculadas com JPA e Hibernate

104

Meu bean Java tem uma propriedade childCount. Esta propriedade não está mapeada para uma coluna do banco de dados . Em vez disso, ele deve ser calculado pelo banco de dados com uma COUNT()função operando na junção de meu bean Java e seus filhos. Seria ainda melhor se essa propriedade pudesse ser calculada sob demanda / "preguiçosamente", mas isso não é obrigatório.

Na pior das hipóteses, posso definir a propriedade deste bean com HQL ou a API Criteria, mas preferiria não fazê-lo.

A @Formulaanotação do Hibernate pode ajudar, mas eu mal consegui encontrar qualquer documentação.

Qualquer ajuda muito apreciada. Obrigado.

François
fonte

Respostas:

162

JPA não oferece nenhum suporte para propriedade derivada, então você terá que usar uma extensão específica do provedor. Como você mencionou, @Formulaé perfeito para isso ao usar o Hibernate. Você pode usar um fragmento SQL:

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

Ou mesmo consultas complexas em outras tabelas:

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

Onde idestá o idda entidade atual.

Vale a pena ler a seguinte postagem do blog: Propriedades derivadas do Hibernate - Desempenho e portabilidade .

Sem mais detalhes, não posso dar uma resposta mais precisa, mas o link acima deve ser útil.

Veja também:

Pascal Thivent
fonte
Obrigado Pascal. Você tem alguma ideia de por que o seguinte não está funcionando? @Formula (value = "(selecione count ( ) da junção interna ic c onde ic.category_id = c.id e c.id = id)") public Integer getCountInternal () {return countInternal; } A consulta está OK e vejo que está sendo executada nos logs. O mapeamento parece OK: main org.hibernate.cfg.Ejb3Column - fórmula de ligação (select count ( ) blah blah blah Mas countInternal permanece definido com seu valor inicial (-1) :( Eu tentei usar anotações de campo em vez de anotações getter, mas ainda não funciona.
François
Obrigado, realmente me ajudou muito na minha tarefa!
Mythul
13
@NeilMcGuigan: A @Formulaanotação usa SQL, não HQL.
user1438038,
7
Acabei de aprender como é importante ter parênteses ao redor da consulta. Sempre acabava com uma Exceção SQL, já que essa consulta, é claro, se torna uma subconsulta quando o objeto host é carregado. O MySQL não gostou da seleção embutida sem parênteses.
desculpemissjackson
1
O novo link para o artigo é: blog.eyallupu.com/2009/07/hibernate-derived-properties.html
Adnan
53

Conforme explicado neste artigo , você tem três opções:

  • ou você está calculando o atributo usando um @Transientmétodo
  • você também pode usar @PostLoadouvinte de entidade
  • ou você pode usar a @Formulaanotação específica do Hibernate

Enquanto o Hibernate permite que você use @Formula , com JPA, você pode usar o retorno de chamada @PostLoad para preencher uma propriedade transitória com o resultado de alguns cálculos:

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

Para consultas mais complexas, você pode usar o Hibernate @Formula, conforme explicado neste artigo :

@Formula(
    "round(" +
    "   (interestRate::numeric / 100) * " +
    "   cents * " +
    "   date_part('month', age(now(), createdOn)" +
    ") " +
    "/ 12) " +
    "/ 100::numeric")
private double interestDollars;
Vlad Mihalcea
fonte
7
No entanto, isso não permite consultas mais complexas
Hubert Grzeskowiak
2
Atualizei a resposta, então agora você também tem uma solução para consultas mais complexas.
Vlad Mihalcea
1

Dê uma olhada no Blaze-Persistence Entity Views, que funciona com base no JPA e fornece suporte DTO de primeira classe. Você pode projetar qualquer coisa em atributos dentro de Visualizações de entidade e até mesmo reutilizará nós de junção existentes para associações, se possível.

Aqui está um exemplo de mapeamento

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

A busca irá gerar uma consulta JPQL / HQL semelhante a esta

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

Aqui está uma postagem de blog sobre provedores de subconsulta personalizados que pode ser interessante para você: https://blazebit.com/blog/2017/entity-view-mapping-subqueries.html

Christian Beikov
fonte
0

eu tentei isso no meu código

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

os erros que recebo quando corro e exibo minha visão antes mesmo de tentar exibir a coluna de bigode é algo assim

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)

a pergunta que fiz foi : há uma maneira de obter valores da coluna Calculada usando JPA / Hibernate com mySQL?

O que estou fazendo de errado ?

bob269
fonte