TSM - Hibernate : o abordare simplă prin exemple

Cătălin Tudose - Java and Web Technologies Expert @ Luxoft Training


Hibernate este un framework Java foarte popular, destinat facilitării accesului la baze de date şi conectării între lumea obiectuală şi lumea tabelelor relaţionale. Articolul nostru îşi propune să ofere o introducere în utilizarea acestui framework.

Urmând paradigma POO, Java gestionează obiectele din memorie, acestea conlucrând pentru rezolvarea problemei. O posibilă dificultate cu care se poate confrunta programatorul este aceea a faptului ca, la terminarea execuţiei programului, informaţia conţinută de acestea să fie pierdută. Astfel de obiecte se numesc tranzitorii.

Noţiunea de persistenţă se referă la posibilitatea de salvare a datelor şi după încetarea execuţiei aplicaţiei. Aproape toate aplicaţiile necesită ca obiectul creat să fie salvat pentru un acces ulterior şi pentru o eventuală modificare.

Prin instanţiere, obiectul devine tranzitoriu în interiorul aplicaţiei. Pentru ca un obiect să devină persistent, acesta trebuie salvat şi făcut disponibil chiar şi după ce aplicaţia încetează să se execute.

Soluţia cea mai răspândită pentru realizarea persistenţei este folosirea bazei de date şi a sistemului pentru gestionarea bazelor de date. Există diferite tipuri de baze de date, în funcţie de modul în care datele sunt organizate şi în funcţie de modul în care le accesăm. Bazele de date relaţionale conţin date stocate în tabele care sunt conectate prin relaţii. În momentul de faţă, cel mai răspândit tip de baze de date este cel relaţional.

În timpul dezvoltării intensive şi a creării sistemelor de stocare şi gestionare a datelor, programarea orientată pe obiecte nu era prea răspândită. Acesta este unul dintre motivele din cauza cărora dezvoltarea sistemului de gestionare a datelor a folosit modelul relaţional. Încă unul dintre motivele pentru care se acordă atenţie în ziua de astăzi bazelor de date relaţionale este şi limbajul prin care sunt gestionate datele în astfel de sisteme - SQL.

Bazele de date relaţionale reprezintă o tehnologie matură care a fost deja dovedită în practică. Aproape fiecare dintre sistemele relaţionale pentru gestionarea bazelor de date, care sunt populare în ziua de astăzi, au în spatele lor o dezvoltare îndelungată şi o utilizare activă. De asemenea, bazele de date relaţionale se bazează pe standarde larg acceptate. Migrarea între două sisteme relaţionale pentru gestionarea bazelor de date nu reprezintă o dificultate.

Pentru conectarea lumii obiectuale cu lumea relațională trebuie dezvoltată o logică care va insera caracteristicile şi stările obiectelor în mod adecvat în baza de date, în aşa fel încât să fie posibilă citirea şi reutilizarea obiectelor. Pentru aşa ceva trebuie respectate unele reguli anterior stabilite. Autorii acestor reguli trebuie să fim noi; de asemenea, oricine ar vrea să lucreze la obiectele noastre trebuie să ştie regulile pe baza cărora se va executa persistenţa în baza de date.

Procesul care reduce decalajul între modelul relaţional şi cel orientat pe obiecte se numeşte mapare obiectual-relaţională. Acest proces are şi prescurtarea de ORM (Object-Relational Mapping). Scopul nostru este să gestionăm datele prin obiectele Java, ceea ce este o situaţie normală din punctul de vedere al unei aplicaţii.

După ce obiectele sunt salvate permanent în baza de date, acestea se pot prelua mai târziu din ea şi reîmpacheta în obiecte pentru a le folosi din nou. Această mapare, de fapt, transformă datele dintr-o reprezentare în alta.

Aceste transformări ale datelor se efectuează la nivelul mediu al aplicaţiei, în partea destinată persistenţei, adică în aşa-numitul strat sau nivel persistent. Echipa de dezvoltare dispune de mai multe posibilităţi de implementare a acestui mecanism. ORM se poate implementa doar cu ajutorul SQL-ului şi JDBC-ului, însă echipa de dezvoltare poate folosi şi un instrument care automatizează acest proces. Instrumentele care automatizează acest proces se numesc instrumente ORM, iar Hibernate este un astfel de instrument.

Dintre avantajele instrumentelor de mapare, menţionăm:

Mentenanţa

Cu ajutorul acestor instrumente se obţine un sistem uşor de întreţinut şi, în primul rând, uşor de înţeles. Această reducere semnificativă a cantităţii de cod reduce şi posibilitatea de erori, dar asigură o mentenanţă uşoară.

Timpul de dezvoltare

Codul care se scrie manual pentru persistenţa obiectelor este ceea ce necesită cel mai mult timp, dar pe de altă parte nu reprezintă nicio provocare pentru programatori. Părţile din aplicaţie legate de persistenţă se scriu pe baza unui şablon, independent de tipul de sistem care se implementează. Utilizarea instrumentelor pentru maparea obiectual-relaţională scurtează timpul pentru acest lucru monoton şi asigură programatorilor axarea asupra părţilor din cod specifice pentru o problemă care se implementează în aşa-numitul business logic.

Portabilitatea

Proiectarea corectă a aplicaţiei software implică faptul că aceasta, în timpul dezvoltării, nu ar trebui să "ştie" cu ce sistem de gestionare a bazei de date (SGBD) va comunica. Aceasta ar trebui să fie independentă de producător şi de versiunea de SGBD. Tocmai aceasta este una dintre caracteristicile mai importante ale aplicaţiilor proiectate în mod corect. Dacă din anumite motive se ajunge la schimbarea SGBD-ului, nici în acest caz nu trebuie să influenţeze aplicaţia software.

Dintre dezavantajele instrumentelor de mapare, menţionăm:

Hibernate asigură o persistenţă foarte uşoară a celor mai simple obiecte Java (POJO - Plain Old Java Object). Hibernate trebuie să cunoască ce tabele şi obiecte sunt în relaţie - aceasta se obţine prin mapare. Maparea defineşte modul de copiere a obiectelor din memoria aplicaţiei în baza de date. Există două moduri de definire a mapării:

Pe baza mapării Hibernate este capabil să realizeze partea sa de operaţie care se referă la salvarea datelor în baza de date. Pentru a face aceasta, Hibernate foloseşte fişierul său de configurare.

Presupunem că avem o aplicaţie care doreşte să opereze cu o librărie, gestionând cărţile acesteia.

O asemenea clasă POJO ar putea arăta astfel:

package model;
public class Book {
  private int id;
  private String author;
  private String title;

  public Book(String author, String title) {
        this.author = author;
        this.title = title;
    }

    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public String getAuthor() { return author; }
    public void setAuthor(String author) {
        this.author = author;
    }
    public String getTitle() { return title; }
    public void setTitle(String title) {
        this.title = title;
    }
}

Configurarea din fişierul Hibernate este prezentată mai jos:

<?xml version="1.0" encoding="UTF-8"?>
<hibernate-configuration>
  <session-factory>
    <property name="hibernate.dialect">
      org.hibernate.dialect.MySQLDialect</property>

    <property 
      name="hibernate.connection.driver_class">
        com.mysql.cj.jdbc.Driver
    </property>

    <property name="hibernate.connection.url">  
    jdbc:mysql://localhost:3306/bookstore
    </property>

    <property name="hibernate.connection.username"> 
    root
    </property>

    <property name="hibernate.connection.password"> 
    </property>

    <property name="hibernate.hbm2ddl.auto">
        create
    </property>

    <mapping class="model.Book"/>
    <mapping resource="config/book.hbm.xml" />

  </session-factory>
</hibernate-configuration>

Fişierul de configurare Hibernate cuprinde câteva informaţii esenţiale:

Maparea este o marcare a datelor pentru ca acestea să fie recunoscute şi utilizate în mod corect. Pentru ca o entitate să fie salvată în baza de date, Hibernate caută fişierul de mapare care defineşte maparea entităţii în baza de date. Acest fişier conţine datele de care Hibernate are nevoie pentru ca să ştie în ce fel va efectua comunicarea cu baza de date, ce tabel să acceseze şi ce coloană să folosească. Pentru clasa Book, maparea XML ar arăta astfel:

<?xml version="1.0" encoding="utf-8"?>
 <hibernate-mapping>
     <class name="model.Book" table="books">
         <id name="id" type="long" column="id">
             <generator class="increment" />
         </id>
         <property name="author" 
           column="author" type="string" />
         <property name="title" 
                   column="title" 
           type="string" />
     </class>
 </hibernate-mapping>

Pentru a fi posibilă pornirea şi punerea în funcţie a Hibernate-ului este necesară existenţa obiectului Session. Acest obiect se foloseşte pentru crearea unei
conexiuni fizice la baza de date.

Persistenţa şi furnizarea obiectelor se execută prin
acest obiect. Pentru obţinerea instanţei obiectelor Session este necesară existenţa obiectelor Configuration
şi SessionFactory. Configurarea Hibernate-ului oferă informaţii despre bazele de date şi mapare.

Obiectul Configuration se foloseşte pentru crearea obiectului SessionFactory care la rândul său permite instanţierea obiectului Session.

Pentru obţinerea obiectului Session se creează o clasă auxiliară care se utilizează oriunde este necesar în aplicaţie. Cel mai des, o astfel de clasă se numeşte HibernateUtil.

public class HibernateUtil {

 private static final SessionFactory sessionFactory;
 private static final ServiceRegistry serviceRegistry;

 static {
   try {
    Configuration configuration = new Configuration();
    configuration.configure("config/hibernate.cfg.xml");
    serviceRegistry = 
     new StandardServiceRegistryBuilder() 
       .applySettings(configuration.getProperties())
       .build();

    sessionFactory = configuration
          .buildSessionFactory(serviceRegistry);

  } catch (Throwable ex) {
    System.err.println(
     "Initial SessionFactory creation failed." + ex);
      throw new ExceptionInInitializerError(ex);
      }
    }

 public static SessionFactory getSessionFactory() {
   return sessionFactory;
 }
}

Utilizarea HibernateUtil şi interacţiunea cu baza de date se produce astfel:

package booksstore;

import model.Book;
import model.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.SessionFactory;

public class BooksStore {
    public static void main(String[] args) {

      SessionFactory sessionFactory = HibernateUtil
         .getSessionFactory();

      Session session = sessionFactory.openSession();
        session.beginTransaction();
        Book book = new Book("Dave Minter", 
                             "Beginning Hibernate");
        session.save(book);
        session.getTransaction().commit();
        session.close();
        System.exit(0);
    }
}

Pentru configurarea şi utilizarea Hibernate prin intermediul anotărilor este necesar să realizăm următoarele modificări:

package model;

import javax.persistence.*;

@Entity
@Table(name="books")
public class Book {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "id")
  private int id;

    @Column(name="author")
    private String author;

    @Column(name="title")
    private String title;

    public Book(String author, String title) {
        this.author = author;
        this.title = title;
    }

    public int getId() {
        return id;
    }

Anotările se scriu în cadrul claselor Java şi conţin marcajul \@ la început. Ele sunt parte a limbajului de programare Java începând cu versiunea 5. Ele pot fi verificate în timpul compilării şi eventual se poate efectua refactoring prin intermediul IDE-ului. În timpul mapării XML nu se poate verifica corectitudinea semantică. Mai mult, sunt mai simple spre deosebire de mapările XML care sunt mai complexe şi au mai mult cod.

Anotările utilizate în exemplul nostru au următoarea semnificaţie:

@Entity - defineşte clasa ca fiind asociată cu o entitate într-o bază de date.

@Table - indică denumirea tabelei cu care se face asocierea.

@Id - defineşte câmpul de identificare (cheia primară din tabelă).

@GeneratedValue - indică faptul că valoarea este generată de baza de date şi cum anume se face această generare.

@Column - indică numele coloanei cu care se face asocierea.

Concluzii

Hibernate este un puternic instrument ORM, a cărui introducere am făcut-o în acest articol. Am pus faţă în faţă cele două metode de a defini meta-informaţia necesară mapării între clasele Java şi tabela din baza de date. Exemplele au condus la crearea asocierii dintre o clasă din program şi o tabelă, reprezentând în acest fel primii paşi de acumulat celor care vor utiliza acest framework.