Semplifica la DAO con Spring e Java Generics

1. Panoramica

Questo articolo si concentrerà sulla semplificazione del livello DAO utilizzando un singolo oggetto di accesso ai dati generato per tutte le entità nel sistema, il che si tradurrà in un elegante accesso ai dati, senza inutili confusione o verbosità.

Costruiremo sulla classe DAO astratta che abbiamo visto nel nostro precedente articolo su Spring e Hibernate e aggiungeremo il supporto per i generici.

2. I DAO Hibernate e JPA

La maggior parte delle basi di codice di produzione ha una sorta di strato DAO. Di solito, l'implementazione varia da più classi senza una classe base astratta a una sorta di classe generata. Tuttavia, una cosa è coerente: ce n'è sempre più di una . Molto probabilmente, esiste una relazione uno a uno tra i DAO e le entità nel sistema.

Inoltre, a seconda del livello di generici coinvolti, le implementazioni effettive possono variare da codice fortemente duplicato a quasi vuoto, con la maggior parte della logica raggruppata in una classe astratta di base.

Queste implementazioni multiple possono in genere essere sostituite da un singolo DAO parametrizzato. Possiamo implementarlo in modo tale che nessuna funzionalità venga persa sfruttando appieno l'indipendenza dai tipi fornita da Java Generics.

In seguito mostreremo due implementazioni di questo concetto, una per un livello di persistenza incentrato su Hibernate e l'altra incentrata su JPA. Queste implementazioni non sono affatto complete, ma possiamo facilmente aggiungere altri metodi di accesso ai dati aggiuntivi.

2.1. The Abstract Hibernate DAO

Diamo una rapida occhiata alla classe AbstractHibernateDao :

public abstract class AbstractHibernateDao { private Class clazz; @Autowired SessionFactory sessionFactory; public void setClazz(Class clazzToSet){ this.clazz = clazzToSet; } public T findOne(long id){ return (T) getCurrentSession().get(clazz, id); } public List findAll() { return getCurrentSession().createQuery("from " + clazz.getName()).list(); } public T create(T entity) { getCurrentSession().saveOrUpdate(entity); return entity; } public T update(T entity) { return (T) getCurrentSession().merge(entity); } public void delete(T entity) { getCurrentSession().delete(entity); } public void deleteById(long entityId) { T entity = findOne(entityId); delete(entity); } protected Session getCurrentSession() { return sessionFactory.getCurrentSession(); } }

Questa è una classe astratta con diversi metodi di accesso ai dati, che utilizza SessionFactory per manipolare le entità.

2.2. Il generico Hibernate DAO

Ora che abbiamo la classe DAO astratta, possiamo estenderla solo una volta. L'implementazione DAO generica diventerà l'unica implementazione di cui abbiamo bisogno:

@Repository @Scope(BeanDefinition.SCOPE_PROTOTYPE) public class GenericHibernateDao extends AbstractHibernateDao implements IGenericDao{ // }

Innanzitutto, si noti che l'implementazione generica è essa stessa parametrizzata , consentendo al cliente di scegliere il parametro corretto caso per caso. Ciò significa che i client ottengono tutti i vantaggi dell'indipendenza dai tipi senza dover creare più artefatti per ogni entità.

In secondo luogo, si noti l'ambito del prototipo di questa implementazione DAO generica . L'uso di questo ambito significa che il contenitore Spring creerà una nuova istanza di DAO ogni volta che viene richiesto (incluso il cablaggio automatico). Ciò consentirà a un servizio di utilizzare più DAO con parametri diversi per entità diverse, secondo necessità.

Il motivo per cui questo ambito è così importante è dovuto al modo in cui Spring inizializza i bean nel contenitore. Lasciare il DAO generico senza un ambito significherebbe usare l'ambito singleton predefinito, che porterebbe a una singola istanza di DAO che risiede nel contenitore . Ciò sarebbe ovviamente fortemente restrittivo per qualsiasi tipo di scenario più complesso.

L'IGenericDao è semplicemente un'interfaccia per tutti i metodi DAO affinché possiamo iniettare l'attuazione occorre:

public interface IGenericDao { T findOne(final long id); List findAll(); void create(final T entity); T update(final T entity); void delete(final T entity); void deleteById(final long entityId); }

2.3. The Abstract JPA DAO

L'AbstractJpaDao è molto simile al AbstractHibernateDao:

public abstract class AbstractJpaDao { private Class clazz; @PersistenceContext EntityManager entityManager; public void setClazz( Class clazzToSet ) { this.clazz = clazzToSet; } public T findOne( Long id ){ return entityManager.find( clazz, id ); } public List findAll(){ return entityManager.createQuery( "from " + clazz.getName() ) .getResultList(); } public void save( T entity ){ entityManager.persist( entity ); } public void update( T entity ){ entityManager.merge( entity ); } public void delete( T entity ){ entityManager.remove( entity ); } public void deleteById( Long entityId ){ T entity = getById( entityId ); delete( entity ); } }

In modo simile all'implementazione DAO di Hibernate, stiamo utilizzando l'API Java Persistence direttamente, senza fare affidamento sull'ormai deprecato Spring JpaTemplate .

2.4. Il generico JPA DAO

Simile all'implementazione di Hibernate, anche l'oggetto di accesso ai dati JPA è semplice:

@Repository @Scope( BeanDefinition.SCOPE_PROTOTYPE ) public class GenericJpaDao extends AbstractJpaDao implements IGenericDao{ // }

3. Iniezione di questo DAO

Ora abbiamo un'unica interfaccia DAO che possiamo iniettare. Dobbiamo anche specificare la classe:

@Service class FooService implements IFooService{ IGenericDao dao; @Autowired public void setDao(IGenericDao daoToSet) { dao = daoToSet; dao.setClazz(Foo.class); } // ... }

Spring autowires la nuova istanza DAO utilizzando setter injection in modo che l'implementazione possa essere personalizzata con l' oggetto Class . Dopo questo punto, il DAO è completamente parametrizzato e pronto per essere utilizzato dal servizio.

Ci sono ovviamente altri modi in cui la classe può essere specificata per DAO - tramite la riflessione o anche in XML. La mia preferenza è verso questa soluzione più semplice a causa della migliore leggibilità e trasparenza rispetto all'utilizzo della riflessione.

4. Conclusione

Questo articolo ha discusso la semplificazione del livello di accesso ai dati fornendo un'unica implementazione riutilizzabile di un DAO generico. Abbiamo mostrato l'implementazione sia in un ambiente basato su Hibernate che in un ambiente JPA. Il risultato è uno strato di persistenza ottimizzato, senza inutili disordine.

Per un'introduzione passo passo sulla configurazione del contesto Spring utilizzando la configurazione basata su Java e il pom Maven di base per il progetto, vedere questo articolo.

Infine, il codice per questo articolo può essere trovato nel progetto GitHub.