Hibernate Tips - Select new(..) ..

Hibernate - Mappare il risultato di una 'Select' su un oggetto
G.Morreale

Introduzione:

Hibernate è una potente libreria per la gestione del mapping oggetti/dati relazionali.
La libreria consente di effettuare una gestione ad oggetti del proprio database relazionale.
In questo post voglio mostrare una semplicissima tecnica che consente allo sviluppatore di mappare il risultato di una select non all'interno di un entity ma all'interno di una propria classe (es. DTO).


L'Esempio

Supponiamo di avere una tabella, TELEFONATE, sul db contenente le seguenti colonne

  • id
  • numero_destinatario
  • numero_mittente
  • note
  • data

Tale tabella è mappata sull'entity Telefonate

@Entity
@Table(name = "telefonate")
public class Telefonate implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @Column(name = "id", nullable = false)
    private Integer id;
    @Column(name = "numero_destinatario", nullable = false)
    private String numeroDestinatario;
    @Column(name = "numero_mittente", nullable = false)
    private String numeroMittente;
    @Column(name = "note", nullable = false)
    private String note;
    @Column(name = "data", nullable = false)
    @Temporal(TemporalType.TIMESTAMP)
    private Date data;
//... più i metodi get/set


Supponiamo adesso di volere creare un session bean tale da interagire con l'entity manager e restituire l'aggregato del conteggio del numero di telefonate per mittente.


native query sql:
SELECT count(*) as c, numero_mittente FROM telefonate t group by numero_mittente;
Il session bean in questione avrà il metodo getAggregateBySender():
package sessionbean;

import dto.AggregateDTO;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Stateless
public class myBeanBean implements myBeanLocal 
{
    @PersistenceContext
    private EntityManager em;

    public AggregateDTO[] getAggregateBySender()
    {
        return null;
    }    
}
Implementazione:
package sessionbean;

import dto.AggregateDTO;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Stateless
public class myBeanBean implements myBeanLocal 
{
    @PersistenceContext
    private EntityManager em;

    public AggregateDTO[] getAggregateBySender()
    {
        //da implementare
        return null;
    }    
}
Quindi i dati che il session bean dovrà esporre mediante il metodo sono il conteggio e il numero del mittente(sender), al fine di trasferire questi due dati(utilizzando il design pattern DTO), si dovrà creare una classe AggregateDTO in grado di trasportare i dati in questione.
Il codice del DTO sarà il seguente:
package dto;

public class AggregateDTO 
{
    
private long count;
    
private String mittente;
    
    public AggregateDTO(long count, String mittente)
    
{
        
this.count = count;
        
this.mittente = mittente;
    
}
    
    public long getCount()
    
{
        
return count;
    
}
    
    public void setCount(long count)
    
{
        
this.count = count;
    
}

    public String getMittente()
    
{
        
return mittente;
    
}

    public void setMittente(String mittente)
    
{
        
this.mittente = mittente;
    
}
}
La query ejb-ql che ci consente di estrarre i dati interessati è la seguente:
SELECT count(*), t.numeroMittente FROM Telefonate t GROUP BY t.numeroMittente
In tal modo però l'entityManager restituirà un array di array (o una list di list o qualcosa di simile a seconda del persistence provider utilizzato), si dovrà in seguito scorrere l'array ed effettuando una serie di casting costruire gli oggetti DTO da restituire.
Si verrebbe quindi a creare una situazione per cui il codice necessario sarebbe il seguente:
    public AggregateDTO[] getAggregateBySender()
    {
        Query q = em.createQuery("SELECT count(*), t.numeroMittente FROM Telefonate t GROUP BY t.numeroMittente");
        List list = q.getResultList();
        int len = list.size();
        AggregateDTO[] array = new AggregateDTO[len];
        
        for (int i = 0; i < len; i++)
        {
           Object[] o = (Object[]) list.get(i);
           array[i] = new AggregateDTO((Long)o[0], (String)o[1]);
        }
        return array;
    }    
Questa versione del metodo sicuramente può essere resa più performante ed elegante utilizzando la sintassi oggetto di questo articolo.
La query sarà riscritta usando direttamente il costruttore new AggregateDTO all'interno della query stessa.
nota:Non è una soluzione standard ejb-ql ma una soluzione possibile solo in hibernate.
La query sarà la seguente
SELECT new dto.AggregateDTO(count(*), t.numeroMittente) FROM Telefonate t GROUP BY t.numeroMittente
Di conseguenza il metodo verrà così riscritto
    public AggregateDTO[] getAggregateBySender()
    {
Query q = em.createQuery("SELECT new dto.AggregateDTO(count(*), t.numeroMittente) FROM Telefonate t GROUP BY 
t.numeroMittente");
        
     AggregateDTO[] array = (AggregateDTO[]) q.getResultList().toArray(new AggregateDTO[0]);        
       return array;
}
Conclusione
L'articolo dimostra come utilizzare una feature di hibernate al fine di scrivere meno codice e in modo più elegante.
Tale tecnica viene spesso utilizzata quando entra in gioco il pattern DTO. Bisogna però prestare attenzione al fatto che la query non è compatibile con lo standard EJB-QL quindi qualora si vuole mantenere la compatibilità cross-persitenceProvider la soluzione non è forse ideale.

No comments: