Ejb3 In Action Cap9: Manipulating entities with EntityManager

9.1 Introducing the EntityManager

Le API dell'entity manager sono la parte più importante e interessante delle Java Persistence API.

9.1.1 The EntityManager interface

L'entity manager è il ponte tra il mondo OO e il mondo relazionale. Quando viene richiesto che è una entità sia creata l'entity manager lo traduce in un nuovo record di db. Lo stesso per tutte le operazioni CRUD. Ma a dispetto di quello che ci può pensare non è una interfaccia complessa ma semplice, intuitiva e piccola.
metodi-entity-manager-1.jpg
metodi-entity-manager-2.jpg

9.1.2 The lifecycle of an entity

Una entità ha un ciclo di vita semplice. L'entity manager non conosce niente circa un POJO di come sia annotato finchè non si dice al manager di iniziare a trattare il POJO come una entità JPA. Queto è l'opposto di quello che avviene con i session beans o con i MDBs che sono caricati e amministrati dal container come l'applicazione parte.
Il comportamento di default dell'Entity manager è gestire (manage) l'entità nel più breve tempo possibile. Questo è l'opposto del container managed beans manage che gestisce (manage) le entità finchè l'applicazione non viene spenta, (dalle mie conoscenze vi è un un pool di session beans pronti che vengono assegnati alle richieste).
Una entità di cui l'EntityManager tiene traccia è considerata attached o managed. Quando l'entity manager fa lo stop della gestione di una entità , questa è detta detached. Una entità che non è stata mai amministrata si chiama transient o new.
Nella figura vi è una

Managed entities

L'entity manager ha il compito di tenere sincronizzata l'entità con il db. Per fare questo si tiene un riferimento all'entità e controlla la "freschezza". Se l'entity manager trova che i dati di una entità sono cambiati automaticamente sincronizza i cambiamenti nel database. Smette si amministrare un'entità quando viene cancellata o viene spostata fuori dalla sua raggiungibilità.
stati-entita.jpg

9.3 Managing persistence operations

9.3.1 Persisting entities

Ecco un codice di esempio per creare ed aggiungere un'entità.

public Item addItem(String title, String description,
    byte[] picture, double initialPrice, long sellerId) {
    Item item = new Item();
    item.setTitle(title);
    item.setDescription(description);
    item.setPicture(picture);
    item.setInitialPrice(initialPrice);
    Seller seller = entityManager.find(Seller.class, sellerId);
    item.setSeller(seller);
    entityManager.persist(item);
    return item;
}

Quando questo metodo viene completato un nuovo record nel db viene istanziato. Una entità può avere delle relazioni con altre entità, in questo esempio vi è una relazione con Seller. Viene recuperata l'entità corrispondente tramite il metodo find che usa la chiave primaria.
Il metodo persist viene invocato per creare una nuovo record nel db usando l'entità e non per aggiornarne una. È importante quindi che la chiave primaria non sia già presente nel db.
Se viene fatto il persist che viola le costanti di integrità del db viene generata un javax.persistence.PersistenceException.
Quando il metodo persist effettua il return l'entità diventa managed. Lo statement INSERT che crea il record corrispondente non è emesso necessariamente immediatamente.
  • Per il transation-scope dell'EntityManager lo statement INSERT viene invocato quando della transazione viene fatto il commit. Nell'esempio visto prima questo vuol dire al momento che il metodo addItem fa il return.
  • Per il extended-scoped (o application-managed) dell'EntityManager lo statement INSERT è lanciato probabilmente prima che l'EntityManager venga chiuso.
  • Lo statement di insert può essere lanciato quando l'EntityManager è flushed.

Discuteremo brevemente del flush automatico (AUTO) o manuale (COMMIT) più avanti. Per adesso basta sapere che conviene per alcune circostanze che il programmatore o l'EntityManager possa scegliere di avere delle operazioni pending sul db (come una INSERT per esempio), senza aspettare che la transazione finisca o che il EntityManager venga chiuso.
Nel codice visto sopra non vi è la chiave primaria perchè è il db che genera una select prima di fare la INSERT questo perchè la tabella è predisposta.
Come visto prima tutte le operazioni di persistenza che richiedono degli update al db devono essere invocati con uno scope di transaction. Se una transazione enclosing non è presente quando il metodo persist è invocato una TransactionRequiredException viene lanciata per una transation-scoped entity manager. Se si sta usando una application-managed per estendere un entity manager, invocando il metodo persist verrà attach l'entità al contesto di persistenza. Se una nuova transazione parte l'entity manager joins la transazione e i cambiamenti vengono salvati sul database quando sulla transazione viene fatto il commit. È vero lo stesso per le operazioni di flush, merge, refresh e remove.

Persisting entity relationships

Come si vede bene nell'esempio sotto, vi è una relazione tra due entità. Dove a livello di entità una ne contiene un'altra.

public User addUser(String username, String email,
    String creditCardType, String creditCardNumber,
        Date creditCardExpiration) {
    User user = new User();
    user.setUsername(username);
    user.setEmail(email);
    BillingInfo billing = new BillingInfo();
    billing.setCreditCardType(creditCardType);
    billing.setCreditCardNumber(creditCardNumber);
    billing.setCreditCardExpiration(creditCardExpiration);
    user.setBillingInfo(billing);
    entityManager.persist(user);
    return user;
}

Quando viene eseguito il metodo persist le chiavi primarie vengono create e anche le chiavi esterne, tutto viene eseguito dal db, con le due insert

Cascading persist operations

Il comportamento di default delle JPA non è quello di generare a cascata le INSERT per le entità collegate. Per capire come mai questo accade per l'entità specificata basta guardare il codice dell'entità User.

public class User {
@OneToOne(cascade=CascadeType.PERSIST)
    public void setBillingInfo(BillingInfo billing) {

L'idea del cascade sulla persistenza realizzata tramite ORM è simile all'idea del cascading in un db. Per default l'elemento cascade è vuoto il che significa che non vengono propagate operazioni di persistenza alle entità collegate alternativamente l'elemento cascade può essere settato ai seguenti valori ALL, MERGE, PERSIST, REFRESH, or REMOVE.

CascadeType Value Effect
CascadeType.MERGE Only EntityManager.merge operations are propagated to related entities.
CascadeType.PERSIST Only EntityManager.persist operations are propagated to related entities.
CascadeType.REFRESH Only EntityManager.refresh operations are propagated to related entities.
CascadeType.REMOVE Only EntityManager.remove operations are propagated to related entities.
CascadeType.ALL All EntityManager operations are propagated to related entities.

Si possono settare le operazioni di cascade solo per alcuni metodi dell'entity manager.

9.3.5 Controlling updates with flush

Controllare il flush può essere utile per ottimizzare le richieste al db, in modo da non inondare con continue query di INSERT per esempio ma raggrupparle tutte in una volta.
Per default il mode di flush è settato ad AUTO , che significa che l'entity manager esegue le operazioni di flush automaticamente quando necessario. In genere questo accade alla fine di una transazione per una transaction-scope oppure quando il contesto di persistenza viene chiuso per una application-managed o per una extende-scope.
In aggiunta a questo se delle entità con cambiamenti pending sono usate in una query, il persistence provider farà il flush dei cambiamenti al database prima di eseguire la query.
Si può settare il flush mode to COMMIT se non ti piace l'idea dell'autoflushing e si vuole avere grande controllo della sincronizzazione del db ecco il metodo per fare questo

entityManager.setFlushMode(FlushModeType.COMMIT);

Se il metodo è settato a COMMIT, il persistence provider sincronizzerà il db solo quando della transazione verrà fatto il COMMIT. Attenzione a non dimenticare il commit perchè se non viene fatto e la query dell'entity manager ritorna entità viziate dal database l'applicazione viene posta in uno stato inconsistente.
In realtà resettare il flush mode è spesso un overkill , questo perchè si può esplicitare il flush all'entity manager quando si vuole usando il metodo.
entityManager.flush();

L'entity manager sincronizza lo stato di ogni entity managed con il database come questo metodo viene invocato. Il flush manuale deve essere usato con moderazione e solo quando è necessario.

9.3.6 Refreshing entities

Ho usato questo codice per aggiornare le entità in maniera multipla. Il set del flush consente di effettuare le effettive scritture tutte in una volta. Il metodo di aggiornamento è il merge

public List<Contact> updateContactList(List<Contact> contactToUpdate) 
{
    LogUtil.log("unpdating multiple Contact instance", Level.INFO, null);
    List <Contact> contactToRet=new ArrayList<Contact>(contactToUpdate.size());
    try
    {
        entityManager.setFlushMode(FlushModeType.COMMIT);
        for (Contact contact : contactToUpdate)
        {
            Contact con=entityManager.merge(contact);
            contactToRet.add(con);
        }
        entityManager.flush();
        entityManager.setFlushMode(FlushModeType.AUTO);
        LogUtil.log("save successful", Level.INFO, null);
    }
    catch (RuntimeException re)
    {
        LogUtil.log("save failed", Level.SEVERE, re);
        throw re;
    }
    return contactToRet;
    }
Salvo diversa indicazione, il contenuto di questa pagina è sotto licenza Creative Commons Attribution-ShareAlike 3.0 License