Ce este Hibernate? Fundamentele implementării Hibernate Core
Publicat: 2013-04-17Hibernate este un proiect cadru de persistență Java open source. Efectuați mapare relațională puternică a obiectelor și baze de date de interogare folosind HQL și SQL.
În general, bibliotecile utilizate pe scară largă sunt bine proiectate și implementate și este foarte interesant să învățăm de la ele câteva bune practici de codare.
Să aruncăm o privire în interiorul bibliotecii de bază hibernate și să descoperim câteva dintre cheile de design ale acesteia.
În această postare, Hibernate Core este analizat de JArchitect
pentru a intra în profunzime în designul și implementarea sa.
Pachet după caracteristică
Package-by-feature
utilizează pachete pentru a reflecta setul de caracteristici. Plasează toate articolele legate de o singură caracteristică (și numai acea caracteristică) într-un singur director/pachet. Acest lucru are ca rezultat pachete cu coeziune ridicată și modularitate ridicată și cu cuplare minimă între pachete. Elementele care lucrează strâns împreună sunt așezate unul lângă celălalt.
Nucleul Hibernate conține multe pachete, fiecare fiind legat de o caracteristică specifică hql, sql și altele.
Cuplare
Cuplarea scăzută este de dorit, deoarece o modificare într-o zonă a unei aplicații va necesita mai puține modificări pe parcursul întregii aplicații. Pe termen lung, acest lucru ar putea reduce mult timp, efort și costuri asociate cu modificarea și adăugarea de noi funcții la o aplicație.
Iată trei beneficii cheie derivate din utilizarea interfețelor:
- O interfață oferă o modalitate de a defini un contract care promovează reutilizarea. Dacă un obiect implementează o interfață, atunci acel obiect trebuie să se conformeze unui standard. Un obiect care folosește un alt obiect se numește consumator. O interfață este un contract între un obiect și consumatorul acestuia.
- O interfață oferă, de asemenea, un nivel de abstractizare care face programele mai ușor de înțeles. Interfețele permit dezvoltatorilor să înceapă să vorbească despre modul general în care se comportă codul fără a fi nevoie să intre în multe detalii detaliate.
- O interfață impune cuplarea scăzută între componente, ceea ce face ușor de protejat consumatorul de interfață de orice modificări de implementare în clasele care implementează interfețele.
Să căutăm toate interfețele definite de Hibernate Core, pentru asta folosim CQLinq
pentru a interoga baza de cod.
1 |
from t in Types where t . IsInterface select t |
Dacă scopul nostru principal este de a impune cuplarea scăzută, există o greșeală comună atunci când folosiți interfețe care ar putea distruge utilitatea utilizării acestora. Este folosirea claselor concrete în loc de interfețe și pentru a explica mai bine această problemă să luăm următorul exemplu:
Clasa A implementează Interfața IA care conține metoda calculate(), clasa de consumator C este implementată așa
1 2 3 4 5 6 7 8 9 10 11 |
public class C < br > { … . public void calculate ( ) { … . . m_a . calculate ( ) ; … . } A m_a ; } |
Clasa C în loc să facă referire la interfața IA, face referire la clasa A, în acest caz pierdem beneficiul de cuplare scăzut, iar această implementare are două dezavantaje majore:
- Dacă decidem să folosim o altă implementare a IA, trebuie să schimbăm codul clasei C.
- Dacă unele metode sunt adăugate la A care nu există în IA, iar C le folosește, pierdem și beneficiul contractual de utilizare a interfețelor.
C# a introdus capacitatea explicită de implementare a interfeței în limbaj pentru a se asigura că o metodă din IA nu va fi apelată niciodată dintr-o referință la clase concrete, ci doar dintr-o referință la interfață. Această tehnică este foarte utilă pentru a proteja dezvoltatorii de a pierde beneficiul utilizării interfețelor.
Cu JArchitect putem verifica acest tip de greșeli folosind CQLinq
, ideea este să căutăm toate metodele din clasele concrete utilizate direct de alte metode.
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 } |
De exemplu, metoda getEntityPersister de la SessionFactoryImpl care implementează interfața SessionFactoryImplementor este vizată de această problemă.
Să căutăm metode care invocă direct SessionFactoryImpl.getEntityPersister.
1 2 3 |
from m in Methods where m . IsUsing ( "org.hibernate.internal.SessionFactoryImpl.getEntityPersister(String)" ) select new { m , m . NbBCInstructions } |

Metode precum SessionImpl.instantiate invocă direct getEntityPersister, în loc să treacă prin interfață, ceea ce distruge avantajul utilizării interfețelor. Din fericire, hibernate core nu conține multe metode care să aibă această problemă.
Cuplare cu borcane externe
Atunci când se folosesc lib-uri externe, este mai bine să verificăm dacă putem schimba cu ușurință o libră terță parte cu alta fără a afecta întreaga aplicație, există multe motive care ne pot încuraja să schimbăm o lib terță parte.
Cealaltă lib ar putea:
- Au mai multe caracteristici
- Mai puternic
- Mai sigur
Să luăm exemplul antlr lib
care obișnuia să analizeze interogările hql și să ne imaginăm că a fost creat un alt parser mai puternic decât antlr, am putea schimba cu ușurință antlr-ul cu noul parser?
Pentru a răspunde la această întrebare, să căutăm ce metode din hibernare îl folosesc direct:
1 2 3 |
from m in Methods where m . IsUsing ( "antlr-2.7.7" ) select new { m , m . NbBCInstructions } |
Și care l-au folosit indirect:
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 } |
Multe metode folosesc antlr direct, ceea ce face ca hibernate core să fie puternic cuplat cu acesta, iar schimbarea antlr cu alta nu este o sarcină ușoară. acest fapt nu înseamnă că avem o problemă în designul hibernate, dar trebuie să fim atenți când folosim o lib terță parte și să verificăm bine dacă o lib terță parte trebuie să fie cuplată sau nu cu aplicația.
Coeziune
Principiul responsabilității unice prevede că o clasă ar trebui să aibă un singur motiv pentru a se schimba. Se spune că o astfel de clasă este coerentă. O valoare LCOM
ridicată indică, în general, o clasă slab coerentă. Există mai multe valori LCOM. LCOM își ia valorile în intervalul [0-1]. LCOMHS (HS înseamnă Henderson-Sellers) își ia valorile în intervalul [0-2]. Rețineți că metrica LCOMHS este adesea considerată ca fiind mai eficientă pentru a detecta tipurile necoezive.
Valoarea LCOMHS mai mare decât 1 ar trebui considerată alarmantă.
În general, clasele mai preocupate de coeziune sunt clasele care au multe metode și domenii.
Să căutăm tipuri care au multe metode și câmpuri.
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 } |
Doar câteva tipuri sunt vizate de această interogare și pentru toate acestea, LCOMHS este mai mic de 1.
Utilizarea adnotărilor
Dezvoltarea bazată pe adnotări scutește dezvoltatorii Java de durerea configurației greoaie. Și oferă-ne o funcție puternică pentru a elibera codul sursă de codul standard. Codul rezultat este, de asemenea, mai puțin probabil să conțină erori.
Să căutăm toate adnotările definite de hibernate core.
1 |
from t in Types where t . IsAnnotationClass && ! t . IsThirdParty select t |
Sunt definite multe adnotări, ceea ce face hibernarea ușor de utilizat de către dezvoltatori, iar durerea de cap a fișierelor de configurare este evitată.
Concluzie
Hibernate Core este un bun exemplu de proiecte open source din care să înveți, nu ezitați să aruncați o privire în el.