Cos'è l'ibernazione? Fondamentale dell'implementazione di Hibernate Core
Pubblicato: 2013-04-17Hibernate è un progetto framework di persistenza Java open source. Esegui potente mappatura relazionale degli oggetti e query sui database utilizzando HQL e SQL.
In generale, le librerie ampiamente utilizzate sono ben progettate e implementate ed è molto interessante imparare da esse alcune migliori pratiche di codifica.
Diamo un'occhiata all'interno della libreria principale di ibernazione e scopriamo alcune delle sue chiavi di progettazione.
In questo post Hibernate Core viene analizzato da JArchitect
per approfondire la sua progettazione e implementazione.
Pacchetto per caratteristica
Package-by-feature
utilizza i pacchetti per riflettere il set di funzionalità. Inserisce tutti gli elementi relativi a una singola funzione (e solo quella funzione) in una singola directory/pacchetto. Ciò si traduce in pacchetti con elevata coesione e modularità e con un accoppiamento minimo tra i pacchetti. Gli elementi che lavorano a stretto contatto vengono posizionati uno accanto all'altro.
Hibernate core contiene molti pacchetti, ognuno è correlato a una specifica funzionalità hql, sql e altri.
Accoppiamento
È auspicabile un basso accoppiamento perché una modifica in un'area di un'applicazione richiederà meno modifiche nell'intera applicazione. A lungo termine, ciò potrebbe ridurre molto tempo, fatica e costi associati alla modifica e all'aggiunta di nuove funzionalità a un'applicazione.
Ecco tre vantaggi chiave derivanti dall'utilizzo delle interfacce:
- Un'interfaccia fornisce un modo per definire un contratto che promuove il riutilizzo. Se un oggetto implementa un'interfaccia, quell'oggetto deve essere conforme a uno standard. Un oggetto che utilizza un altro oggetto è chiamato consumatore. Un'interfaccia è un contratto tra un oggetto e il suo consumatore.
- Un'interfaccia fornisce anche un livello di astrazione che rende i programmi più facili da capire. Le interfacce consentono agli sviluppatori di iniziare a parlare del modo generale in cui si comporta il codice senza dover entrare in molti dettagli dettagliati.
- Un'interfaccia impone un basso accoppiamento tra i componenti, cosa che rende facile proteggere l'utente dell'interfaccia da qualsiasi modifica dell'implementazione nelle classi che implementano le interfacce.
Cerchiamo tutte le interfacce definite da Hibernate Core, per questo utilizziamo CQLinq
per interrogare la base di codice.
1 |
from t in Types where t . IsInterface select t |
Se il nostro obiettivo principale è quello di imporre un basso accoppiamento, c'è un errore comune quando si utilizzano interfacce che potrebbero uccidere l'utilità del loro utilizzo. È l'uso delle classi concrete invece delle interfacce, e per spiegare meglio questo problema prendiamo il seguente esempio:
La classe A implementa l'interfaccia IA che contiene il metodo calcola(), la classe consumer C è implementata in questo modo
1 2 3 4 5 6 7 8 9 10 11 |
public class C < br > { … . public void calculate ( ) { … . . m_a . calculate ( ) ; … . } A m_a ; } |
La Classe C invece di fare riferimento all'interfaccia IA, fa riferimento alla classe A, in questo caso perdiamo il vantaggio del basso accoppiamento e questa implementazione presenta due grossi inconvenienti:
- Se decidiamo di utilizzare un'altra implementazione di IA, dobbiamo cambiare il codice della classe C.
- Se alcuni metodi vengono aggiunti ad A non esistenti in IA e C li utilizza, perdiamo anche il vantaggio contrattuale dell'utilizzo delle interfacce.
C# ha introdotto la funzionalità di implementazione esplicita dell'interfaccia nel linguaggio per garantire che un metodo dall'IA non venga mai chiamato da un riferimento a classi concrete, ma solo da un riferimento all'interfaccia. Questa tecnica è molto utile per impedire agli sviluppatori di perdere il vantaggio dell'utilizzo delle interfacce.
Con JArchitect possiamo controllare questo tipo di errori usando CQLinq
, l'idea è di cercare tutti i metodi da classi concrete utilizzate direttamente da altri metodi.
1 2 3 4 5 6 7 8 9 |
from m in Methods where m . NbMethodsCallingMe > 0 && m . ParentType . IsClass && ! m . ParentType . IsThirdParty && ! m . ParentType . IsAbstract let interfaces = m . ParentType . InterfacesImplemented from i in interfaces where i . Methods . Where ( a = > a . Name == m . Name && a . ParentType ! = m . ParentType ) . Count ( ) > 0 select new { m , m . ParentType , i } |
Ad esempio, il metodo getEntityPersister di SessionFactoryImpl che implementa l'interfaccia SessionFactoryImplementor è interessato da questo problema.
Cerchiamo metodi che invocano direttamente SessionFactoryImpl.getEntityPersister.
1 2 3 |
from m in Methods where m . IsUsing ( "org.hibernate.internal.SessionFactoryImpl.getEntityPersister(String)" ) select new { m , m . NbBCInstructions } |

Metodi come SessionImpl.instantiate invocano direttamente getEntityPersister, invece di passare dall'interfaccia, cosa che interrompe il vantaggio dell'utilizzo delle interfacce. Fortunatamente il core di ibernazione non contiene molti metodi che hanno questo problema.
Abbinamento con vasi esterni
Quando vengono utilizzate librerie esterne, è meglio verificare se possiamo facilmente modificare una libreria di terze parti con un'altra senza influire sull'intera applicazione, ci sono molte ragioni che possono incoraggiarci a modificare una libreria di terze parti.
L'altra lib potrebbe:
- Avere più funzioni
- Più potente
- Più sicuro
Prendiamo l'esempio di antlr lib
che usava per analizzare le query hql e immaginiamo che sia stato creato un altro parser più potente di antlr, potremmo cambiare facilmente antlr con il nuovo parser?
Per rispondere a questa domanda cerchiamo quali metodi da ibernazione lo usano direttamente:
1 2 3 |
from m in Methods where m . IsUsing ( "antlr-2.7.7" ) select new { m , m . NbBCInstructions } |
E quali lo hanno utilizzato indirettamente:
1 2 3 4 |
from m in Projects . WithNameNotIn ( "antlr-2.7.7" ) . ChildMethods ( ) let depth0 = m . DepthOfIsUsing ( "antlr-2.7.7" ) where depth0 > 1 orderby depth0 select new { m , depth0 } |
Molti metodi usano antlr direttamente ciò che rende il core di ibernazione altamente accoppiato con esso, e cambiare antlr con un altro non è un compito facile. questo fatto non significa che abbiamo un problema nella progettazione dell'ibernazione, ma dobbiamo stare attenti quando si utilizza una lib di terze parti e controllare bene se una lib di terze parti deve essere accoppiata o meno con l'applicazione.
Coesione
Il principio della responsabilità unica afferma che una classe dovrebbe avere una, e una sola, ragione per cambiare. Si dice che una tale classe sia coesa. Un valore LCOM
elevato generalmente indica una classe scarsamente coesa. Esistono diverse metriche LCOM. LCOM prende i suoi valori nell'intervallo [0-1]. LCOMHS (HS sta per Henderson-Sellers) assume i suoi valori nell'intervallo [0-2]. Si noti che la metrica LCOMHS è spesso considerata più efficiente per rilevare i tipi non coesi.
Un valore LCOMHS maggiore di 1 dovrebbe essere considerato allarmante.
In generale le classi più interessate dalla coesione sono le classi che hanno molti metodi e campi.
Cerchiamo tipi con molti metodi e campi.
1 2 3 4 |
from t in Types where ( t . Methods . Count ( ) > 40 | | t . Fields . Count ( ) > 40 ) && t . IsClass orderby t . Methods . Count ( ) descending select new { t , t . InstanceMethods , t . Fields , t . LCOMHS } |
Solo pochi tipi sono interessati da questa query e per tutti il LCOMHS è inferiore a 1.
Utilizzo delle annotazioni
Lo sviluppo basato su annotazioni allevia gli sviluppatori Java dal dolore di una configurazione ingombrante. E forniscici una potente funzionalità per liberare il codice sorgente dal codice standard. È anche meno probabile che il codice risultante contenga bug.
Cerchiamo tutte le annotazioni definite da hibernate core.
1 |
from t in Types where t . IsAnnotationClass && ! t . IsThirdParty select t |
Vengono definite molte annotazioni, ciò che rende l'ibernazione facile da usare da parte degli sviluppatori e viene evitato il mal di testa dei file di configurazione.
Conclusione
Hibernate Core è un buon esempio di progetti open source da cui imparare, non esitare a dare un'occhiata al suo interno.