Cómo mapear propiedades calculadas con JPA e Hibernate

104

Mi bean Java tiene una propiedad childCount. Esta propiedad no está asignada a una columna de la base de datos . En cambio, debería ser calculado por la base de datos con una COUNT()función operando en la unión de mi bean Java y sus hijos. Sería incluso mejor si esta propiedad pudiera calcularse bajo demanda / "perezosamente", pero esto no es obligatorio.

En el peor de los casos, puedo establecer la propiedad de este bean con HQL o la API Criteria, pero preferiría no hacerlo.

La @Formulaanotación de Hibernate puede ayudar, pero apenas pude encontrar documentación.

Cualquier ayuda muy apreciada. Gracias.

Francois
fuente

Respuestas:

162

JPA no ofrece ningún soporte para la propiedad derivada, por lo que deberá usar una extensión específica del proveedor. Como mencionaste, @Formulaes perfecto para esto cuando usas Hibernate. Puede utilizar un fragmento de SQL:

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

O incluso consultas complejas en otras tablas:

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

Donde ides el idde la entidad actual.

Vale la pena leer la siguiente publicación del blog: Propiedades derivadas de Hibernate: rendimiento y portabilidad .

Sin más detalles, no puedo dar una respuesta más precisa, pero el enlace anterior debería ser útil.

Ver también:

Pascal Thivent
fuente
Gracias Pascal. ¿Tiene alguna idea de por qué lo siguiente no funciona? @Formula (value = "(seleccione count ( ) de ic inner join c donde ic.category_id = c.id y c.id = id)") public Integer getCountInternal () {return countInternal; } La consulta está bien y veo que se está ejecutando en los registros. El mapeo parece correcto: org.hibernate.cfg.Ejb3Column principal - fórmula de enlace (seleccione count ( ) blah blah blah Pero countInternal permanece establecido en su valor inicial (-1) :( He intentado usar anotaciones de campo en lugar de anotaciones de captador, pero todavía no funciona.
Francois
Gracias, ¡realmente me ayudó mucho con mi tarea!
Mythul
13
@NeilMcGuigan: La @Formulaanotación usa SQL, no HQL.
user1438038
7
Acabo de aprender lo importante que es tener los paréntesis alrededor de la consulta. Siempre terminaba con una excepción SQL, ya que esta consulta, por supuesto, se convierte en una subconsulta cuando se carga el objeto host. A MySQL no le gustó la selección en línea sin paréntesis.
sorrymissjackson
1
El nuevo enlace al artículo es: blog.eyallupu.com/2009/07/hibernate-derived-properties.html
Adnan
53

Como se explica en este artículo , tiene tres opciones:

  • o está calculando el atributo usando un @Transientmétodo
  • también puedes usar el @PostLoaddetector de entidades
  • o puede usar la @Formulaanotación específica de Hibernate

Si bien Hibernate le permite usar @Formula , con JPA, puede usar la devolución de llamada @PostLoad para completar una propiedad transitoria con el resultado de algún cálculo:

@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 más complejas, puede usar Hibernate @Formula, como se explica en este artículo :

@Formula(
    "round(" +
    "   (interestRate::numeric / 100) * " +
    "   cents * " +
    "   date_part('month', age(now(), createdOn)" +
    ") " +
    "/ 12) " +
    "/ 100::numeric")
private double interestDollars;
Vlad Mihalcea
fuente
7
Sin embargo, esto no permite consultas más complejas
Hubert Grzeskowiak
2
Actualicé la respuesta para que ahora también tenga una solución para consultas más complejas.
Vlad Mihalcea
1

Eche un vistazo a Blaze-Persistence Entity Views que funciona sobre JPA y proporciona soporte DTO de primera clase. Puede proyectar cualquier cosa a los atributos dentro de Vistas de entidad e incluso reutilizará los nodos de unión existentes para asociaciones si es posible.

Aquí hay un mapeo de ejemplo

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

Obtener esto generará una consulta JPQL / HQL similar 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

Aquí hay una publicación de blog sobre proveedores de subconsultas personalizadas que también pueden ser interesantes para usted: https://blazebit.com/blog/2017/entity-view-mapping-subqueries.html

Christian Beikov
fuente
0

he probado esto en mi código

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

los errores que obtengo cuando ejecuto y muestro mi vista incluso antes de intentar mostrar la columna en el bigote son algo como esto

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 pregunta que hice fue ¿hay alguna manera de obtener valores de la columna calculada usando JPA / Hibernate con mySQL?

Qué estoy haciendo mal ?

bob269
fuente