Showing posts with label Hibernate(Italiano). Show all posts
Showing posts with label Hibernate(Italiano). Show all posts

MySQL VS Oracle XE

Oracle XE vs MySQL
G.Morreale

Introduzione:

Dopo aver installato e provato(vedi precedenti articoli) la versione Express di Oracle è arrivato il momento di vedere se le prestazioni sono migliori o meno rispetto al noto dbms MySQL.

Scenario

Allora recupero la mia solita applicazione Java EE di test, costituita da un modulo EJB e un modulo WEB.
Il modulo EJB dotato di un session bean e un entity bean.

L'entity bean mappa la tabella esempiotable costituita da due colonne (id (INTEGER AUTOINC.), testo(STRING)).
Il persistence provider utilizzato è hibernate.
L'entitymanager utilizzato è un JTA Entity Manager ottenuto tramite dependency injection.
Il modulo Web è dotato di una servlet che ha il compito di richiamare il session bean presente nel modulo EJB.

Il test è una comparazione delle performance sia per quanto riguarda le operazioni di inserimento sul db che per ciò che concerne l'estrazione dei dati(SELECT).

Quindi il session bean(e relativa interfaccia locale) è dotato di due metodi:

  • Uno per l'inserimento di 'n' entityBean

    public void batchInsert(int nRows)
    {
        for (int i = 0; i < nRows; i++)
        {
            em.persist(new Esempiotable());            
        }
    }

  • l'altro per l'estrazione di tutti gli entityBean corrispondenti a tutte le righe della tabella di test

    public void listAll()
    {                
        List<Esempiotable> list = em.createNamedQuery("Esempiotable.ALL").getResultList();
        for (Esempiotable t: list)
        { System.out.println(t.getId());}        
    }


L'ambiente di sviluppo utilizzato è netbeans 6.1 dal quale sfrutterò il profiler per raccogliere i risultati ottenuti.
L'application server utilizzato è Glassfish V2.

Driver JDBC Mysql: mysql-connector-java-5.1.6-bin.jar
Driver JDBC Oracle:ojdbc14.jar

In Mysql storage engine utilizzato: "Inno DB"

L'Entity Bean

L'entity bean come già accennato è il mapping di una semplice tabella dotata di una colonna integer autoincrement e una colonna di testo.
Per cui il codice (valido per il mapping nei confronti di mysql) è:


@Entity
@Table(name = "esempiotable")
@NamedQueries(
{
   @NamedQuery(name = "Esempiotable.ALL", query = "SELECT e FROM Esempiotable e")
})
public class Esempiotable implements Serializable {
    private static final long serialVersionUID = 1L;
        
    @Id   
    @GeneratedValue(strategy=GenerationType.IDENTITY)    
    @Column(name = "id", nullable = false)
    private Integer id;
    
    @Column(name = "testo", nullable = true)
    private String testo;
//...omessi metodi accessor e costruttore


Tale mapping funziona correttamente su MySQL, ma utilizzandolo con Oracle si ottiene il seguente errore:

...
Caused by: java.lang.IllegalArgumentException: Dialect does not support identity key generation
        at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:223)
        at com.sun.enterprise.util.EntityManagerWrapper.persist(EntityManagerWrapper.java:440)
...

Ciò è dovuto al fatto che l'autoincrement di Oracle è gestito attraverso le sequenze, ovvero oggetti del db che indicano l'algoritmo di generazione e incremento della chiave primaria.
Quindi  "GenerationType.IDENTITY" deve essere sostituito con "GenerationType.SEQUENCE"

Tale modifica non è sufficente infatti si ottiene il seguente errore:

Caused by: javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not get next sequence value

L'errore si verifica perchè hibernate non è stato istruito circa quale sequence utilizzare.
Quindi attraverso SQLDeveloper andiamo a leggere il nome del sequence generator e indichiamolo attraverso le opportune annotation:

In definitiva il codice

@GeneratedValue(strategy=GenerationType.IDENTITY)  

Verrà sostituito con il seguente nel momento in cui il test dovrà essere eseguito su dbms oracle xe.

@GeneratedValue(strategy=GenerationType.AUTO, generator="ESEMPIOTABLE_ID_SEQ")
@SequenceGenerator(name="ESEMPIOTABLE_ID_SEQ", sequenceName="ESEMPIOTABLE_ID_SEQ")    

nota: E' stato utilizzato il metodo AUTO piuttosto che SEQUENCE per maggiore portabilità rispetto a futuri cambiamenti.


Gli Inserimenti

(Per la lettura dei risultati leggere il selftime dei metodi batchInsert o listAll)

Il primo test prevede un inserimento di 100.000 entity all'interno del db:
Vediamo subito i risultati:

Oracle XE


MySQL
Ripetiamo il test con 500.000

Oracle XE

MySQL

L'estrazione
Analizziamo i risultati di estrazione di 500.000 elementi:
Oracle XE
MySQL

Conclusione

Questi test sono da prendere con le pinze in quanto l'architettura del sistema dbms, application server, sistema operativo e quant'altro non è quella relativa a un sistema in "produzione".

Il setup e il tuning dei due dbms è quello standard, ovvero non è stata apportata alcuna modifica dopo l'installazione alla configurazione dei relativi dbms.

Inoltre i test sono relativi solo a 2 case e non prevedendo altri scenari tipici di un applicazione.

Fatte queste premesse, possiamo dire che mysql sia in fase di inserimento che in fase di estrazione ha dato dei risultati nettamente migliori.

Personalmente questi risultati nonostante siano basati su delle prove concrete mi lasciano un pò perplesso, in quanto mi aspetto di più lato Oracle. Se qualcuno che conosce bene Oracle XE può darci qualche consiglio per rifare i test in virtù di alcuni settaggi o accorgimenti ben venga.


Hibernate: Caricare migliaia di righe

Hibernate: getResultList() vs ScrollableResults G.Morreale
Introduzione:
Se sul db ci sono migliaia di righe in una tabella e intendiamo caricare i corrispettivi entity(magari per effettuare un batch update), potremmo trovarci di fronte a delle scelte al fine di ottimizzare l'uso della memoria.
Le soluzioni adottate sono due.
  1. Usare un JTA EntityManager, richiamare una namedQuery e ottenere la collection dei risultati con getResultList() (richiamato sull'oggetto Query)
  2. Usare l'oggetto session di hibernate e appoggiarsi all'oggetto ScrollableResults per ottenere i risultati
Esempio
Supponiamo di avere il solito Entity Esempiotable(composto da due campi: int:id e string:testo)
Supponiamo che ci siano sul db 500.000 righe (per le quali hibernate genera 500.000 Entity)
La namedQuery utilizzata è la seguente:
@NamedQuery(name = "Esempiotable.ALL", query = "SELECT e FROM Esempiotable e")
Codice Soluzione 1:
public void listAll()
{
List<Esempiotable> list = em.createNamedQuery("Esempiotable.ALL").getResultList();
list.get(0);
}
Codice Soluzione 2:
public void scrollableResultTest()
{
Configuration configuration = new Configuration();
SessionFactory sf = configuration.buildSessionFactory();
Session ses = sf.openSession();
ScrollableResults res = (ScrollableResults) ses.getNamedQuery("Esempiotable.ALL").scroll();
res.next();
}
Quindi i metodi al confronto sono "getResultList" e "scroll".
Leggendo le javadoc non ci sono molti dettagli circa la funzionalità di tali metodi, il primo carica i risultati della query nella struttura dati List, il secondo invece permette di ottenere un iterator in grado di estrarre i risultati spostandosi attraverso dei cursori all'interno del result iterator stesso.
Quindi forse il metodo migliore per coglierne effettivamente le differenze è quello di lanciare il profiler e analizzare il consumo di memoria:
Nel caso di getResultList vengono allocati 2.839.152 B (immagine sopra)
Nel caso invece di scroll vengono allocati 12.840 B (immagine sotto)
Quindi è facile notare come il consumo di memoria nel caso di inizializzazione della List e dello ScrollableResult sia nettamente minore nel secondo caso.
nota:
Il supporto della scrollabilità dei risultati dipende dal driver jdbc sottostante.
E le performance?
Diamo un occhio all'immagine sopra, il metodo listAll() rappresenta la soluzione 1, mentre scrollableResultTest() rappresenta la soluzione 2.
Come è facile notare l'inizializzazione della seconda struttura dati è molto più veloce rispetto alla prima.
nota:
(il divario resta, anzi aumenta anche nel caso di invocazioni multiple dei metodi)
Però..
Se una volta ottenute le corrispettive strutture dati si vuole fare un ciclo al fine di operare sugli entity via via estratti dalla struttura i risultati si ribaltano:
(metodo eseguito su 500.000 rows)
Infatti estrarre i risultati dalla scrollableList richiede una quantità di memoria minore, nonchè un inizializzazione più veloce, ma in fase di estrazione risulta essere più lento.
Conclusione
Come in ogni caso la scelta della soluzione dipende dal contesto.
Se si intende risparmiare sulla memoria e penalizzare leggermente le performance si può optare per la soluzione "scrollable", altrimenti se non si hanno problemi di memoria (macchine con molta molta ram, su cui è configurato una max heap size) notevole allora si può caricare tutto in memoria e operare sui vari dati già estratti.
Tutto dipende..

Hibernate: Ottenere la Session

Hibernate: Come inizializzare una Session
G.Morreale

Introduzione:

Dovendo fare diversi esprimenti utilizzando hibernate, mi sono trovato davanti all'inizializzazione dell'oggetto Session di hibernate al fine di utilizzarlo al posto dell'EntityManager.

Il Codice

Passiamo direttamente a vedere il codice che commenterò di seguito passo per passo.

        AnnotationConfiguration configuration = new AnnotationConfiguration();
        configuration.addResource("META-INF/hibernate.cfg.xml");
        configuration.addPackage("entities");        
        configuration.addAnnotatedClass(entities.Esempiotable.class);       
        configuration.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");        
        configuration.setProperty("hibernate.connection.datasource", "esempioJNDI");        
        SessionFactory sf = configuration.buildSessionFactory();        
        Session ses = sf.openSession();   

La prima riga di codice serve a inizializzare la Configuration.

AnnotationConfiguration configuration = new AnnotationConfiguration();

Essa è la rappresentazione della configurazione di hibernate.
Tale configurazione spesso viene inserita all'interno di un file xml, per cui non appena inizializzato l'oggetto lo si può aggiornare con i settaggi presenti nel file xml di configurazione

configuration.addResource("META-INF/hibernate.cfg.xml");

Il metodo addResource è utilizzato quando il file di configurazione si trova nel classpath (ad es. dentro il jar)

Ma torniamo alla prima riga di codice: Perchè AnnotationConfiguration piuttosto che Configuration?
La scelta di questa specializzazione della classe è dovuta al mapping.
Hibernate è un ORM framework, quindi deve conoscere il mapping tra i dati relazionali (le tabelle ad esempio) e i corrispettivi entity.
Tale mapping può essere configurato in diversi modi

  • file xml di configurazione
  • settando le oppurtune proprietà sull'istanza di configuration
  • Attraverso le annotation

Se il progetto, come nel caso dell'esempio, ha un mapping effettuato attraverso annotation, si può aggiornare la configuration indicando il nome del package e/o della classe che ha le annotazioni che provvedono al mapping.

Istruzioni per indicare a hibernate dove trovare il mapping dell'Entity Esempiotable
configuration.addPackage("entities");        
configuration.addAnnotatedClass(entities.Esempiotable.class);

nota:
Nel caso in cui il mapping non è configurato si ottiene il seguente errore nello stack trace:
.... Caused by: org.hibernate.MappingException: ....

andiamo avanti:
Il dialect, se tale proprietà non viene settata hibernate genera un eccezione

..HibernateException: Hibernate Dialect must be explicitly set..

Ovviamente tale settaggio, come tutti gli altri, può essere inserito all'interno del file xml.

Se non si setta opportunamente il datasource si ottiene il seguente errore:

....The user must supply a JDBC connection.....

Quindi bisogna fornire a hibernate indicazioni circa le connessioni jdbc.
Esse di solito vengono settate attraverso le seguenti proprietà dal chiaro significato.

  • hibernate.connection.driver_class
  • hibernate.connection.url
  • hibernate.connection.username
  • hibernate.connection.password

etc. etc.

Ma nel caso, come l'esempio, in cui si vuole utilizzare una risorsa (JNDI) dell'application pool è possibile settare il JNDI name (e altre proprietà opzionali) per indicare a hibernate di estrarre la connessione attraverso la risorsa del container.
L'istruzione per far ciò è la seguente.
       
configuration.setProperty("hibernate.connection.datasource", "esempioJNDI");
        
Le ultime due istruzioni sono banali, esse servono una a creare una SessionFactory, ovvero un oggetto thread-safe in grado di generare session l'altra invece serve a generare una sessione, una unit of work, un'oggetto NON thread safe in grado di rappresentare la conversazione con il persistence context e il db.

        SessionFactory sf = configuration.buildSessionFactory();        
        Session ses = sf.openSession();   

Una volta ottenuto l'oggetto session si possono eseguire query, operazioni di find, merging, save etc. date un occhio alla javadoc di hibernate: http://www.hibernate.org/hib_docs/v3/api/org/hibernate/Session.html


Conclusione

Al fine di creare del codice il più ordinato possibile la configurazione dovrebbe risidiere nel file xml opportunamente costruito secondo le specifiche di hibernate.
Nonostante ciò ho voluto mostrare come creare una session aggiungendo alcune proprietà mancanti sull'istanza della configurazione.
Inoltre le proprietà mancanti non sono necessarie nel caso in cui si usa l'entityManager piuttosto che l'oggetto session.

Hibernate: Inserire migliaia di righe senza avere problemi di m

Hibernate: Inserimenti Batch senza OutOfMemoryException G.Morreale
Introduzione:
In questo breve articolo voglio mostravi appena due righe di codice per rendere persistenti migliaia di Entity senza intasare la memoria.
Nel titolo è presente il nome di hibernate.. in quanto è il framework più usato, ma la soluzione è valida anche per altri persistence provider.
Esempio
Supponiamo di voler eseguire il seguente codice di inserimento
Esempiotable t;
for (int i = 0; i < 500000; i++)
{
//Creazione nuovo entity (non passo alcun parametro perchè l'id è autoincrement)
t = new Esempiotable();
//Memorizzazione entity all'interno del persistence Context attraverso em(EntityManager)
em.persist(t);
}
In questa porzione di codice ad ogni creazione dell'entity esso verrà inserito all'interno del persistence context, ciò ad ogni ciclo incrementa il consumo di bytes all'interno dell'heap.
Effettuando un test del precedente codice attraverso il profiler di netbeans è possibile notare come il consumo di memoria massimo durante il ciclo si aggiri intorno ai 250MB per un Entity dotato di soli due campi (Integer e String (peraltro lasciato vuoto))
Se anzichè 500000 entity ne creassimo molti di più otterremmo un Eccezione di tipo OutOfMemoryException
La Soluzione
Qual'è il "contenitore" che si riempie? Il PERSISTENCE CONTEXT!!
Quindi è necessario di tanto in tanto svuotarlo.
Esempiotable t;
for (int i = 0; i < 500000; i++)
{
//Creazione nuovo entity (non passo alcun parametro perchè l'id è autoincrement)
t = new Esempiotable();
//Memorizzazione entity all'interno del persistence Context attraverso em(EntityManager)
em.persist(t);
if ((i % 20) == 0)//Ogni venti inserimenti
{ em.flush();//rendiamo persisteni le modifiche (non necessario se em.flushModeType = COMMIT
em.clear();//ripuliamo il persistence context
}
}
Attraverso il flushing e relativo clear il consumo di memoria nella stessa situazione provata prima non supera i 48MB.
Ciò dimostra che il metodo funziona non solo teoricamente ma anche nella pratica.
nota:
Nel caso sia attiva una cache di secondo livello, essa deve essere disabilitata per le operazioni batch.
Conclusione
La tecnica "flush and clear" funziona anche nel caso di update, in tal caso se si usa hibernate si può optare per lo ScrollableResults in modo da usare i cursori per velocizzare il processo di estrazione dei dati e non intasare la memoria nel caricamento di troppi dati nel persistence context.
Inoltre come consigliato nella guida hibernate, bisogna settare nella configurazione, la seguente proprietà:
hibernate.jdbc_batch_size = 20;
in modo da abilitare il batch processing a livello jdbc.
Riferimenti.

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.