Sei sulla pagina 1di 8

Web Application Engineering

Object Relational Mapping


Cristian Lucchesi
Istituto di Informatica e Telematica - CNR

ORM
Wikipedia [OR Mapping - Object-Relational mapping is the process of the transformation of the data between the class objects and databases. Applications can depend on an OR-M like tool that greatly simplifies this work instead of manually coding the transformation process.]

Introduzione a Java Persistence API


Java Persistence API (JPA) fornisce POJO (Plain Old Java Object) standard e object relational mapping (OR mapping) per dati persistenti. la persistenza riguarda: il salvataggio dei dati la loro consultazione la loro gestione (aggiornamento, cancellazione) i dati adesso possono essere gestiti tramite JPA a partire da EJB 3.0 come risultato della JSR 220

Persistence Entities
con Persistent Data normalmente ci si riferisce a dati permanenti in una applicazione lo stato dei dati reso permanente salvandolo in uno storage come un Database, un filesystem, una flash memory, ... in JPA ci si riferisce ai dati persistenti come Entity per esempio il titolo, l'abstract, l'articolo, la data di una entry di un blog sono dati tipicamente permanenti e possono essere raggruppati in una entity BlogEntry

Pojo
in termini JPA una entit un oggetto persistente che pu; essere salvato e consultato da uno storage in termini tecnici, questa entity corrisponde ad una classe Java public class BlogEntry { private String id; private String title; private String excerpt; private String body; private Date date; ... } // Getters e Setters vanno qui

la classe corrisponde ad un oggetto BlogEntry con attributi: it, titolo, excerpt, ...

Entity
per rendere persistente la classe java utilizzando JPA necessario qualificarla come una entity utilizzando l'annotazione @Entity l'annotazione dice al motore della persistenza che gli oggetti creati da quella classe sono supportati dalla JPA @Entity public class BlogEntry { private String id; private String title; private String excerpt; private String body; private Date date; ... }

@Table
@Table utilizzato a livello della classe permette di specificare i nomi delle tabella, del catalogo, dello schema relativi al mapping se l'annotazione @Table non presente il default (in Hibernate) il nome non qualificato della classe @Entity

@Entity @Table(name="blog_entries") public class BlogEntry { ... }

@Column
@Column utilizzato a livello di propriet o relativo getter permette di specificare gli attributi della colonna su cui la propriet mappata se l'annotazione @Column non presente il nome della colonna (in Hibernate) uguale al nome della propriet @Column(name="id", nullable=false) private String id; @Column(name="title", nullable=false, length=70) private String title; @Column(name="excerpt", nullable=false, length=200) private String excerpt; ... altri attributi impostabili sulla colonna sono: unique, insertable, updatable, precision, scale

@Id
una entity deve essere sempre identificabile univocamente si pu utilizzare un tipo primitivo od un oggetto apposito come chiave univoca il metodo equals verr chiamato dalla JPA per confrontare l'uguaglianza di due chiavi l'annotazione @Id su un campo (o sul suo setter) marca un campo come chiave @Id private String id;

Auto-generation of Primary Keys


A primary key for an entity which is usually annotated with @Id annotation can be given a value manually or we can depend on the persistence provider for the same. For this we have to use the @GeneratedValue annotation. @Id @GeneratedValue(strategy = GenerationType.AUTO) private String imeiNo; Since the imeiNo is going to be the primary key for the mobile object, we have decorated the field with @GeneratedValue annotation, which delegates the burden of creating values from developers to the persistence engine. Also, there are 4 different methods (or strategies) for primary key generation, which are AUTO, IDENTITY, SEQUENCE and TABLE. The simplest one is the automatic primary key generation strategy which is represented as GenerationType.AUTO.

BlogEntry Entity
import import import import java.util.Date; javax.persistence.Column; javax.persistence.Entity; javax.persistence.Id;

@Entity public class BlogEntry { @Id @Column(name="id", nullable=false) private String id; @Column(name="title", nullable=false, length=70) private String title; @Column(name="excerpt", nullable=false, length=200) private String excerpt; @Column(name="body", nullable=false, length=1400) private String body; @Column(name="date", nullable=false) private Date date; //Getter, setter e altri metodi di seguito...

EntityManager
questa classe segue il pattern Manager per gestire le entit gestire una o pi entit si riferisce all'azione di mantenere un insieme di oggetti Java sotto il controllo dell'EntityManager fino a quando le entit non hanno un'associazione con l'EntityManager esse sono solamente normali oggetti java anche se marcati con l'annotazione @Entity

EntityManager - J2SE
l'EntityManager fornisce una API per rendere persistenti le entit , rimuoverle, aggiornarle, interrogarle e cancellarle nelle applicazioni J2SE, un riferimento all'EntityManager pu essere ottenuto tramite un Factory EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("PersistentUnitName"); EntityManager eManager = entityManagerFactory.createEntityManager(); L'EntityManagerFactory pu essere configurato con l'aiuto della Persistent Unit di cui parleremo pi avanti.

EntityManager - J2EE
nelle applicazioni J2EE il container inietter direttamente un riferimento all'entityManager utilizzando la dependency injection import javax.persistence.EntityManager; ... @PersistenceContext private EntityManager entityManager;

EntityManager.persist()
gli oggetti annotati come entit sono regolari oggetti java fino a quando non vengono resi persistenti dall'EntityManager ... BlogEntry blogEntry = new BlogEntry(); blogEntry.setId("waeSeminar") blogEntry.setTitle("Seminario di Web application Engineering") // Update delle varie propriet entityManager.persist(blogEntry); ... il metodo persist(entityObject) rende persistente l'entit nel database (esegue la INSERT SQL) quando la persist viene invocato viene controllato che non ci siano oggetti con lo stesso id nel database, altrimenti viene sollevata una run-time exception: EntityExistsException

EntityManager.find()
il metodo find utilizzabile per interrogare gli entity object il metodo find() accetta due paremetri l'oggetto Entity il valore della chiave primaria se l'oggetto richiesto non pu essere trovato, l'entityManager restituisce null BlogEntry entry = entityManager.find(BlogEntry.class, "waeSeminar"); If (entry != null){ // entry object may or may not be null. // Process the object. } l'oggetto restituito dall'EntityManager diventa cos direttamente utilizzabile

EntityManager.getReference()
accetta gli stessi parametri del metodo find se l'oggetto non trovato questo metodo restituisce un'eccezione EntityNotFFoundException l'istanza prelevata in modo lazely (non si prelevano i valori delle propriet dal database) lo stato dell'entit (title, excerpt, body...) sono prelevati la prima volta che si accede all'oggetto BlogEntry entry = entityManager.getReference(BlogEntry.class, "waeSeminar"); // entry object may not contain the actual state values for title, excerpt, // body and date, the states may be loaded during the first access. String title = entry.getTitle(); // The persistence engine may fetch the title value for the entry here // at this particular point of time.

// at this particular point of time. ...

EntityManager.remove()
per cancellare un oggetto dal database possibile utilizzare il metodo EntityManager.remove(entityObject) l'entityObject passato deve essere gestito dall'entityManager altrimenti la rimozione fallisce l'operazione di cancellazione dal db pu avvenire successivamente (dopo la flush()) dopo la chiamata, l'entity diventer "detached" dal entityManager e non pi da lui gestita BlogEntry entry = entityManager.getReference(BlogEntry.class, "waeSeminar"); entityManager.remove(entry)

Entity e Transazioni
All entities have the property of transactionability and their CRUD operations will take place within a transactional context. Transactions can be broadly classified into two types based on who actually owns (or manages) the transaction. They are JTA and Resource-local transaction. In the case of a J2EE Application, the default transaction type is JTA (Java Transaction API), unless explicitly specified. A method can be simply annotated with @RequiresNew (or @Requires) in which case a new transaction will always started by the container and the transaction completes as soon as the method ends. Whereas in a J2SE application, the transaction-type defaults to Resource-local, which means that the developer (or the application code) has to explicitly start and end a transaction. It means that the user has to explicitly start a transaction with the help of EntityManager object, and then have to commit it, if everything goes normal, else have to roll-back the transaction if some error occurs.

EntityManager.merge()
The EntityManager.merge(entityObject) method will make a detached entity to get associated with the current persistence context of the EntityManager. // Transaction has begin. ... BlogEntry entry = entityManager.find(BlogEntry.class, "waeSeminar"); ... // Transaction ends. entry.setTitle("Web Application Engineering - Tutorial") // Updating the entry object. entityManager.merge(entry);

In the above piece of code, a mobile entity object is located with the EntityManager.find() method. This entity is now in a managed state. Assume that the transaction ends after some point of time and also the persistence context is a transaction-scoped persistence context. As soon as the transaction completes, the persistence context will go off, (since the persistence context is a transaction-scoped persistence context). So the entity object becomes detached from the persistence context. After this any modifications that are made to the mobile object won't be knowledgeable to the EntityManager as the mobile object has already been detached from it. Now, calling the merge() method, will make the mobile object becomes managed, and all the recent changes that are made to it will become visible to the current persistence context of the EntityManager.

Flushing and Refreshing


il metodo entityManager.flush() sincronizza tutti i cambiamenti delle entity sul db il metodo entityManager.refresh() al contrario preleva le informazioni dal database e aggiorna le entity, eventuali nuovi valori impostati sull'oggetto vengono persi. BlogEntry entry = .... entry.setExcerpt("Seminario ..."); // Aggiorna lo stato dello oggetto .... entityManager.flush(); // Sincronizza il database con i valori // presi dall'oggetto "entry" BlogEntry entry = ... entry.setBody("Il web..."); // Lo stato dell'oggetto "entry" aggiornato ... entityManager.refresh(); // l'entity object viene aggiornato // con i valori presi dal db // Tutti i cambiamenti fatti sono persi

Query API
uno degli svantaggi dei metodi find() e getReference(), che si possono effettuare interrogazioni solo per chiave primaria inoltre il nome della classe deve essere noto a priori le Query API della JPA sono molto pi potenti e molti pi criteri possono essere specificati a runtime l'EntityManager viene utilizzato come factory per ottenere un riferimento ad un oggetto Query il linguaggio per le stringhe utilizzate per le query chiamato JPQL, molto simile all'SQL ma pi object-oriented, robusto e flessibile

Static Query
una static query (o named query) una query definita staticamente prima dell'entity class gli viene assegnato un nome in modo che sia rintracciabile dagli altri componenti che vogliono utilizzarla

gli viene assegnato un nome in modo che sia rintracciabile dagli altri componenti che vogliono utilizzarla @NamedQuery(name="BlogEntry.findAll" query="SELECT be FROM BlogEntry be") @Entity class BlogEntry { } una "named query" cos definita pu essere utilizzata successivamente: Query findAllQuery = entityManager.createNamedQuery("BlogEntry.findAll"); // Execute the query. Note that the code is referring the query by its name ("BlogEntry.findAll") by calling the createNamedQuery() method. Also, since these named queries are statically defined, during deployment time itself, the persistent engine may parse and translate the JPQL into native SQL, cache them, thereby yielding better performance. Multiple named queries can be logically defined with the help of @NamedQueries, like this, @NamedQueries( { @NamedQuery(name="BlogEntry.selectAllQuery" query="SELECT be FROM BlogEntry"), @NamedQuery(name="BlogEntry.deleteAllQuery" query="DELETE FROM BlogEntry") } ) [The @NamedQueries is very similar like @NamedQuery, but can accept multiple @NamedQuery objects, The definition of the @NamedQueries looks like this,

Dynamic Queries
dynamic queries sono quelle in cui la query string viene fornita a runtime per crearle si utilizza entityManager.createQuery(queryString) sono meno efficienti delle named query perch sia il parsing che la validazione della queryString che la trasformazione da JPQL a SQL sono fatte a runtime String queryString = ... // Obtained during run-time. Query dynaQuery = entityManager.createQuery(queryString);

Single result
Query query = entityManager.createQuery( "SELECT be FROM BlogEntry be " + "WHERE be.title = "Web Application Engineering"); BlogEntry entry = query.getSingleResult(); la query string definita dentro la createQuery viene trasformata in sql nella clausola FROM invece del nome della tabella di usa il nome dell'entity "WHERE be.title = ...." indica che l'attributo title deve avere il valore specificato la getSingleResult() esegue la query e restituisce una singola riga di risultato (un solo oggetto) se non sono presenti entity che corrispondono alla query viene sollevata una eccezione EntityNotFoundException se ci sono pi righe che corrispondono viene sollevata una NotUniqueResultException

Multiple Results
query.getResultList() esegue la query e restituisce una lista di istanze di entity (oppure una lista vuota) Query query = entityManager.createQuery("SELECT be FROM BlogEntry"); List<BlogEntry> entries = (List<BlogEntry>query.getResultList(); il type-cast (List<BlogEntry>) necessario perch viene restituita una lista non parametrizzata di Object se solamente una BlogEntry corrisponde ai criteri della query viene restituita una lista di dimensione 1 getResultList() pu eseguire solo operazioni di SELECT, se si utilizza statement di UPDATE o DELETE viene sollevata una IllegalStateException a run-time

Lavorare con i parametri


per riutilizzare ed eseguire le query efficientemente con differenti insiemi di valori in JPA possibile utilizzare il supporto ai parametri: posizionali nominali i posizionali sono utilizzati per sostituire il valore in funzione dell'indice della posizione del parametro e sono marcati con ?index similmente i nominali sono utilizzati per sostituire il valore ad uno specifico termine marcato con :name String selectQuery = "SELECT be FROM BlogEntry WHERE be.title = ?1 and be.excerpt = ?2; Query selectQuery = entityManager.createQuery(selectQuery); ... selectQuery.setParameter(1, title); selectQuery.setParameter(2, excerpt);

durante l'esecuzione della query ?1 e ?2 saranno rimpiazzati dai valori specificati dalle stringhe title e exceprt

durante l'esecuzione della query ?1 e ?2 saranno rimpiazzati dai valori specificati dalle stringhe title e exceprt

Parametri con nome


al parametro pu essere dato un nome significativo prefissato da : String query = "SELECT be FROM BlogEntry WHERE be.title = :title " + " AND be.date = :fromDate" Query selectQuery = entityManager.createQuery(query); selectQuery.setParameter("title", title); selectQuery.setParameter("fromDate", myDate); il metodo createQuery() si occupa anche di trasformare le date nel corretto formato per l'SQL

Named parameter e @NamedQuery


i parametri posizionali e quelli con nome possono essere utilizzati anche nelle static query @NamedQuery(name ="BlogEntry.findByTitle" query = "SELECT be FROM BlogEntry WHERE be.title = :title ) ... //E successivamente nel codice... Query namedQuery = entityManager.createNamedQuery("BlogEntry.findByTitle"); namedQuery.setParameter("title", titleValue);

Paginare i risultati
nel caso una query restituisca molti risultati non una buona idea mostrarli tutti insieme all'utente, potrebbe rallentare molto la visualizzazione mostrare i risultati suddivisi in pagine (come in Google) risolve il problema private static int maxRecords = 25; ... public List<BlogEntry> getPagedBlogEntries(int startPosition) { String queryString = "SELECT be FROM BlogEntry"; Query query = entityManager.createQuery(queryString); query.setMaxResults(maxRecords); query.setFirstResult(startPosition); } return entityManager.getResultList(queryString);

l'applicazione si pu occupare di chiamare il metodo getPagedBlogEntries() passando la posizione iniziale (startPosition) scelta dall'utente

Flushing Query objects


JPA provides two modes of flushing query objects namely AUTO and COMMIT (which are defined in FlushModeType Enumeration). If the AUTO mode is set on the Query object which is the default one, then any changes made to entity objects will be reflected the very next time when a select query is made. That means that the persistence engine must make sure to update all the states of the entity objects which will affect the results of a SELECT query. The AUTO flush mode can be set explicitly on the Query by calling setFlushMode(). queryObject.setFlushMode(FlushModeType.AUTO); When the flush mode is set to COMMIT, then the persistence engine, may only update all the state of the entities during the database COMMIT. The dirty changes applied to entity objects and then querying for the results of the entities may be unspecified. queryObject.setFlushMode(FlushModeType.COMMIT); [The method setFlushMode(FlushModeType) is available in both EntityManager and Query interface. If the EntityManager has been configured with setFlushMode(FlushModeType.AUTO) and the Query object has been called with setFlushMode(FlushModeType.COMMIT), then the preference will always goes to the Query object.]

Configurare la persistenza
ogni applicazione che vuole utilizzare la JPA deve specificare un file persistence.xml nella directory META-INF il file persistence.xml configura l'EntityManagerFactory e di conseguenza l'EntityManager // Nel caso di J2SE String persistentUnitName = "MyBlogPersistentUnit"; EntityManagerFactoryfactory factory =

EntityManagerFactoryfactory factory = Persistence.createEntityManagerFactory(persistentUnitName); EntityManager entityManager = factory.createEntityManager(); // Nel caso J2EE @PersistenceContext(unitName = "MyBlogPersistentUnit") private EntityManager entityManager;

Persistence.xml
Esempio di file persistence.xml

<?xml version="1.0" encoding="UTF-8"?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1 version="1.0"> <persistence-unit name="entityManager"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <jta-data-source>java:/blogDatasource</jta-data-source> <properties> <property name="hibernate.hbm2ddl.auto" value="create-drop"/> <property name="jboss.entity.manager.factory.jndi.name" value="java:/blogEntityManagerFactory"/> <property name="hibernate.jdbc.charSet" value="utf-8"/> </properties> </persistence-unit> </persistence>

Java DataSource
i file datasource permettono di configurare l'accesso ad una sorgente di dati, tipicamente un database sono reperibili tramite un nome specificato con l'elemento jndi-name <?xml version="1.0" encoding="UTF-8"?> <datasources> <local-tx-datasource> <jndi-name>blogDatasource</jndi-name> <connection-url>jdbc:hsqldb:.</connection-url> <driver-class>org.hsqldb.jdbcDriver</driver-class> <user-name>sa</user-name> <password></password> </local-tx-datasource> </datasources> </div>

Molteplicit nelle relazioni


ci sono quattro tipi di molteplicit One-to-one: ogni istanza dell'entity correlata ad una singola istanza di un'altra entity One-to-many: una singola istanza di un'entity pu essere legata a molte istanze di un'altra entity Many-to-one: molte istanze di un'entity sono correlate ad una singola istanza di un'altra entity Many-to-many: le istanze di un'entity possono essre correlate a molte istanze di un'altra entity

esempio di ManyToOne
molte istanze di un oggetto Booking possono essere associate ad un oggetto Customer (UML) @Entity @Table(name = "bookings", schema = "public") public class Booking { ... private Customer customer; ... @ManyToOne @JoinColumn(name = "customer_id", nullable = false) @NotNull public Customer getCustomer() { return this.customers; } ....

....

Relazioni inverse
un Customer pu avere associati molti Booking l'attributo mappedBy dell'annotazione @OneToMany specifica qualle attributo della classe Booking utilizzato per definire la relazione @Entity @Table(name = "customers", schema = "public") public class Customer { ... private List<Booking> bookings; ... @OneToMany(mappedBy = "customer") public List<Booking> getBookings() { return this.bookings; } ...

aiuto!
qualcuno mi aiuta con tutte queste annotazioni??? Hibernate tool pu generare le entity per noi a partire da un database....

Riferimenti
Introduction to Java Persistence API(JPA): http://www.javabeat.net/javabeat/ejb3/articles/2007/04/introduction_to_java_persistence_api_jpa_ejb_3_0_1 The Java EE 5 Tutorial: http://java.sun.com/javaee/5/docs/tutorial/doc/index.html

The end
Grazie per l'attenzione