lunedì 5 marzo 2012

Un mix tecnologico tra JAVA-J2EE/JPA Hibernate e Spring

L'articolo che state per consultare è uno studio di specifiche e framework basati su tecnologia Java.

Lo scopo di questa ricerca è stato realizzare un applicativo distribuito utilizzando le cutting edge technologies o tecnologie all'avanguardia.
Quando si parla di tecnologie all'avanguardia nell'ambiente java, il pensiero ci porta immediatamente alle specifiche J2EE, ma soprattutto alle specifiche principali che ne fanno parte come EJB e JPA. Oltre a queste specifiche, ci sono delle piattaforme e framework che implementano meccanismi di iniezione DI e IOC con il supporto di sistemi e provider di persistenza che inizialmente possono risultare difficili da configurare per le nostre particolari esigenze, ma, nel corso dello sviluppo di un progetto di complessità media o alta, possono portare dei vantaggi significativi accelerando il rilascio dei diversi moduli architetturali che costituiscono il sistema.

Personalmente mi sono sempre domandato come abbinare i framework spring, hibernate, jpa e gli enterprise java beans, affinché potessi ottenere il massimo da ognuna di queste tecnologie all'avanguardia.
Ad esempio, mi sono chiesto come potevo fare per riuscire a fornire una totale configurazione dei miei componenti di servizio, persistenza (ecc...) tramite il framework spring e soprascrivere il meccanismo di iniezione svolto dagli intercettori forniti nella specifica EJB, permettendo a quelli di spring di subentrare in questo processo.

Un'osservazione importante che riporto in questo articolo sono gli aspetti relativi alla normalizzazione delle banca dati: spesso ci imbattiamo nella progettazione di entità di persistenza in JPA, senza mai prendere in considerazione la normalizzazione presente nel sistema relazionale. Non bisogna mai dimenticare che tale normalizzazione deve essere estesa nel contesto applicativo, altrimenti commettiamo un gravissimo errore di progettazione.

Dunque, l'intenzione dell'articolo è spiegare come avviene la progettazione di questo mix tecnologico tramite un pratico esempio .
Attualmente la soluzione è stata da me collaudata su glassfish, ma nulla impedisce la possibilità di schierare l'applicazione su JBoss od altri AS con supporto EJB container.

Pre requisiti:
Glassfish 3v
Jdk 1.6 o +
Hibernate 3 o +
Springframework qualsiasi versione
Specifiche J2EE (EJB 3.0, JPA 1.0/2.0)



Struttura del progetto:



Intefaccia DAO

package com.soluzionijava.sample.dao;

public interface ClienteDAO {
public Cliente findCliente(BigInteger idCliente);
public List<Pagamento> findPagamentiByIdCliente(BigInteger idCliente);
}
L'implementazione dell'interfaccia ClienteDAO

package com.soluzionijava.sample.dao;

public class ClienteDAOImpl implements ClienteDAO {
@PersistenceContext
private EntityManager em;
@Override
public Cliente findCliente(BigInteger idCliente) {
Cliente cliente = null;
String namedQuery = "findClienteById";
try {
Query query = em.createNamedQuery(namedQuery);
query.setParameter(1, idCliente);
cliente = (Cliente)query.getSingleResult();
} catch (NoResultException e) {
cliente = null;
}
return cliente;
}
…...........
}

Ci sono degli elementi che spiccano all'interno di questa implementazione, uno di questi è l'utilizzo dell'annotazione @PersistenceContext: tale annotazione mette a disposizione nella nostra implementazione DAO l'elemento JPA EntityManager. Tramite l'EntityManager vengono effettuate tutte le operazioni d'accesso al dato come ad esempio:

javax.persistence.Query query = em.createNamedQuery(namedQuery);

Viene trovato il Cliente per id tramite JPA EntityManger che a sua volta attraverso il metodo createNamedQuery ci permette di effettuare l'esecuzione di EJB QL.
In questo particolare esempio viene utilizzato quello per le invocazioni delle query nominate ossia namedQuery fornite nell'entità coinvolta nell'operazione, ma, questo prototipo applicativo, non si sofferma sui particolari di JPA, ma bensì sulla progettazione di quest'insieme di tecnologie.

Osserviamo un po' più da vincino l'entità Cliente, entità principale nel nostro articolo:

package com.soluzionijava.sample.entity;

@Entity(name="Cliente")
@Table(name="clienti")
@NamedQuery(name="findClienteById",
query="select o from Cliente o where o.id = ?")
public class Cliente implements java.io.Serializable {

@Id
private BigInteger id;

@Column(name="COGNOME", nullable=false)
private String cognome;

@Column(name="NOME", nullable=false)
private String nome;

@OneToMany(fetch=FetchType.EAGER, mappedBy="cliente")
private Set<RClientiPagamenti> clientiPagamenti;

[…]
}


Il frammento di codice di sopra riportato ci mostra l'entità Cliente con i suoi diversi campi(Field), i setters e getters sono stati omessi, ma devono essere obbligatoriamente presenti.
  • Prima osservazione: @Entity(name="Cliente"). Questa dichiarazione annotata determina che ovunque verrà utilizzata questa entità dovrà essere chiamata con questo nome.
  • Seconda osservazione: @Table(name="clienti"). Correla l'entità ad una tabella relazionale.
  • Terza osservazione: @NamedQuery(name="findClienteById",
    query="select o from Cliente o where o.id = ?"). Definisce ed associa all'entità una NAMED_QUERY.
  • Quarta osservazione: ogni singolo campo viene associato ad un campo di una tabella relazionale.

File di configurazione ORM:

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings version="2.0" xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_2_0.xsd">
<persistence-unit-metadata>
<persistence-unit-defaults>
<access>FIELD</access>
</persistence-unit-defaults>
</persistence-unit-metadata>
</entity-mappings>


Andando avanti nel nostro mix tecnologico, arriviamo finalmente al servizio e cioè l'enterprise java bean che viene iniettato da spring con l'implementazione del nostro DAO.

package com.soluzionijava.sample.service;

@Stateless(mappedName="ClienteService", name="ClienteService")
@Interceptors(SpringBeanAutowiringInterceptor.class)
public class ClienteService implements ClienteServiceRemote, ClienteServiceLocal {
@Autowired
private ClienteDAO clienteDAO;

[…]
@Override
public Cliente findCliente(BigInteger idCliente) {
return clienteDAO.findCliente(idCliente);
}

@Override
public List<Pagamento> findPagamentiByIdCliente(BigInteger idCliente){
return clienteDAO.findPagamentiByIdCliente(idCliente);
}

[…]
}

In questa classe ci sono due elementi di fondamentale importanza, ma discutiamoli uno alla volta affinché tale importanza possa essere capita esaustivamente.
In questo componente avviene la vera manifestazione di mischia tecnologica promessa in questo articolo: se guardiamo con attenzione possiamo osservare che gli intercettori vengono sovrascritti con quelli del framework spring @Interceptors(SpringBeanAutowiringInterceptor.class).

Una volta che spring è subentrato nel meccanismo di iniezione, lungo il corpo della classe possono essere osservate le sue manifestazioni attraverso l'utilizzo dell'annotazione @Autowired.

Per completare è rendere finalmente possibile questo mix, bisogna però fornire le configurazioni contestuali che mettono a disposizione l'intero meccanismo di intercezione ed iniezione(DI/IOC).
Una volta che i nostri componenti java sono pronti per l'uso, bisogna portare nel contesto applicativo una serie di file.
Di seguito vi elenco i file mancanti che chiudono il puzzle:

Il file beanRefContext.xml

<beans>
<bean class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg value="classpath*:soluzionijava-enterprise.xml" />
</bean>
</beans>

Questo file definisce le factory nel contesto business, in effetti viene usato dagli ejb per usufruire dei beans e pojo definiti nel contesto business.

Il file soluzionijava-enterprise.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans>
<import resource="classpath*:soluzionijava-dao.xml"/>
<context:annotation-config/>
<tx:annotation-driven />
</beans>

Il file soluzionijava-dao.xml

<beans>
<bean id="clienteDAO" class="com.soluzionijava.sample.dao.ClienteDAOImpl" autowire="byName"/>
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<property name="persistenceUnitName" value="soluzioniJava"/>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<context:component-scan base-package="com.soluzionijava.sample.entity" />
<tx:annotation-driven />
</beans>





Per l'ultimo il file di persistenza:
persistence.xml
<persistence>
<persistence-unit name="soluzioniJava"
transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>com.soluzionijava.sample.entity.Cliente</class>
<class>com.soluzionijava.sample.entity.RClientiPagamenti</class>
<class>com.soluzionijava.sample.entity.Pagamento</class>
<class>com.soluzionijava.sample.entity.RClientiPagamentiPK</class>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/clienti_db"/>
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
<property name="hibernate.connection.username" value="root"/>
<property name="hibernate.connection.password" value="password"/>
</properties>
</persistence-unit>
</persistence>

Promemoria:
Ricordate di includere nelle intestazioni dei file di configurazione i diversi schemi che occorrono per portare nel contesto applicativo le capacità dichiarate all'interno dei file.

Prima di salutarvi volevo ricordarvi che questo mix di tecnologie è stato da me collaudato e ed attualmente esistono dei progetti nei quali è stato già applicato.

Buon lavoro e al prossimo articolo

Douglas Oviche

Nessun commento:

Posta un commento