Hibernate


Wstęp

Hibernate to framework stworzony do realizacji warstwy dostępu do danych (ang. persistance layer). Zapewnia on przede wszystkim translację danych pomiędzy relacyjną bazą danych, a światem obiektowym (ang. O/R mapping). Opiera się na wykorzystaniu opisu struktury danych za pomocą języka XML lub adnotacji, dzięki czemu można "rzutować" klasy Java bezpośrednio na istniejące tabele bazy danych. Dodatkowo odpowiednio skonfigurowany Hibernate zwiększa wydajność operacji na bazie danych dzięki buforowaniu i minimalizacji liczby przesyłanych zapytań.

jPALIO zostało zintegrowane z Hibernate za pomocą konektora Hibernate, dodatkowego modułu Hibernate oraz nowego typu obiektów jPALIO. Hibernate jest wspierany w wersjach jPALIO >=7.2

Konektor Hibernate

Głównym zadaniem konektora palio.connectors.hibernate.HibernateConnector jest stworzenie fabryki sesji Hibernate (SessionFactory) na podstawie podanych parametrów konfiguracyjnych.W ramach każdej instancji jPALIO można skonfigurować dowolną liczbę konektorów Hibernate. Mapowanie klas na obiekty bazy danych może odbywac się na dwa sposoby. Poprzez pliki XML lub poprzez adnotacje. Integracja Hibernate z jPALIO w większym stopniu wspiera mapowanie poprzez adnotacje, stąd jest to zalecany sposób mapowania.

Opis parametrów konfiguracyjnych konektora Hibernate znaleźć można w sekcji Dokumentacja/Konektory/Hibernate.

Jeżeli definicja klas mapujących przechowywana jest w obiektach jPALIO, nie ma potrzeby wskazywania tych klas w konfiguracji konektora (parametr mapClasses). Dla tych klas powinien być użyty specjalny typ obiektów jPALIO JPA Mapping Class. W czasie konfiguracji/rekonfiguracji konektora Hibernate, wszystkie obiekty jPALIO tego typu są pobierane z bazy danych a następenie dodawane do konfiguracji konektora do którego są przypisane (patrz adnotacja Assigned). Parametry z konfiguracji konektora Hibernate są ważniejsze od parametrów z pliku/obiektu konfiguracyjnego Hibernate. Parametry z pliku/obiektu są nadpisywane przez wartości parametrów konektora.

Ważne! Wszystkie klasy mapujące muszą zostać przypisane do konektora Hibernate. Przypisanie musi zostać wykonane za pomocą adnotacji @Assigned. Klasy, które nie są przypisane do żadnego konektora są pomijane.

Konfiguracja Hibernate

Konfigurację Hibernate dla danego konektora można umieszczać w:

  1. zewnętrznym pliku konfiguracyjnym (patrz parametr configFile),
  2. w treści obiektu jPALIO (patrz parametr configObject),
  3. bezpośrednio w konfiguracji konektora (patrz parametry hibernate.*).


Specyfikację parametrów konfiguracyjnych Hibernate można znaleźć pod adresem: http://docs.jboss.org/hibernate/stable/core/reference/en/html/session-configuration.html

Uwaga! Jeżeli serwer jPALIO, na którym wykorzystywany jest konektor hibernate, nie ma dostępu do pliku podanego w tagu <!DOCTYPE ...> (w poniższym przykładzie jest to "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"), gdyż np. pracuje w zamkniętej sieci, to należy zakomentować cały znacznik lub podać alternatywny adres dostępu do pliku, gdyż nieudana próba pobrania pliku spowoduje błąd parsowania konfiguracji konektora hibernate, a tym samym niemożność korzystania z takiego konektora.

Przykładowa konfiguracja:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
         "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
         "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
  <!-- Database connection settings -->
  <!-- These parameters are specified in connector configuration
  <property name="connection.driver_class">org.postgresql.Driver</property>
  <property name="connection.url">jdbc:postgresql://localhost:5432/workshop</property>
  <property name="connection.username">devel</property>
  <property name="connection.password">devel</property>
  <property name="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</property> 
  -->
 
  <!-- Use the C3P0 connection pool. -->
  <property name="connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
  <property name="hibernate.c3p0.min_size">3</property>
  <property name="hibernate.c3p0.max_size">10</property>
  <property name="hibernate.c3p0.timeout">1800</property>
 
  <!-- Disable second-level cache. -->
  <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
  <property name="cache.use_query_cache">false</property>
  <property name="cache.use_minimal_puts">false</property>
  <property name="max_fetch_depth">3</property>
 
  <!-- JDBC connection pool (use the built-in) -->
  <!-- <property name="connection.pool_size">1</property> -->
 
  <!-- Enable Hibernate automatic session context management -->
  <!-- Bind the getCurrentSession() method to the thread. -->
  <property name="current_session_context_class">thread</property>
 
  <property name="hibernate.show_sql">true</property>
  <property name="hibernate.format_sql">true</property>
  <!-- <property name="hibernate.hbm2ddl.auto">update</property>  -->
</session-factory>
</hibernate-configuration>

Moduł Hibernate

Moduł Hibernate (klasa: palio.modules.Hibernate) jest modułem standardowym dołączanym do każdej instancji jPALIO. Używanie jego ma sens tylko wtedy kiedy w danej instancji istnieje skonfigurowany przynajmniej jeden konektor Hibernate. Głównym zadaniem modułu Hibernate jest zapewnienie dostępu do sesji Hibernate stworzonej za pomocą fabryki sesji wykreowanej w konektorze typu Hibernate.

Moduł zapewnia dwie podstawowe metody:

Adnotacja @Assigned

Adnotacja @Assigned została stworzona z myślą o możliwości przypisywania dowolnych klas do komponentów jPALIO. Wspierane komponenty to:


W przypadku integracji jPALIO z Hibernate za pomocą tej adnotacji wskazuje się do jakiego konektora powinna być przypisana klasa z adnotacjami JPA. Każdy obiekty typu JPA Mapping Class musi zawierać poniższą adnotację.

@Assigned(connector="connector_name")

 

Obiekty jPALIO typu JPA Mapping Class

Za pomocą typu JPA Mapping Class jPALIO jest w stanie określić, które z klas zdefiniowanych w obiektach jPALIO są klasami odpowiedzialnymi za mapowanie. Dzięki wprowadzeniu nowego typu obiektu nie ma potrzeby umieszczania klas mapujących w konfiguracji konektora Hibernate. Każdy z obiektów tego typu powinien zawierać adnotację @Assigned.

Implementacja przykładowego obiektu dla tabeli P_USERS:

package palio.model;
 
import palio.annotations.*;
import javax.persistence.*;
 
@Assigned(connector="connector_name")
 
@Entity
@Table(name = "p_users", uniqueConstraints = @UniqueConstraint(columnNames = "login"))
public class PUser implements java.io.Serializable {
 
    private long id;
    private char status;
    private String language;
    private Date expireDate;
    private Short expireSessions;
    private Short maxSessions;
    private Integer sessionTimeout;
    private Integer maxSessionDurat;
    private Date lastLogin;
    private Date lastWrongLogin;
    private String login;
    private byte[] password;
    private Set<PSession> pSessions = new HashSet<PSession>(0);
 
    public PUser() {}
 
    public PUser(long id, char status, String login) {
        this.id = id;
        this.status = status;
        this.login = login;
    }
 
    public PUser(long id, char status, String language, Date expireDate, Short expireSessions, Short maxSessions, Integer sessionTimeout,
            Integer maxSessionDurat, Date lastLogin, Date lastWrongLogin, String login, byte[] password, Set<PSession> pSessions) {
        this.id = id;
        this.status = status;
        this.language = language;
        this.expireDate = expireDate;
        this.expireSessions = expireSessions;
        this.maxSessions = maxSessions;
        this.sessionTimeout = sessionTimeout;
        this.maxSessionDurat = maxSessionDurat;
        this.lastLogin = lastLogin;
        this.lastWrongLogin = lastWrongLogin;
        this.login = login;
        this.password = password;
        this.pSessions = pSessions;
    }
 
    @Id
    @Column(name = "id", unique = true, nullable = false, precision = 12, scale = 0)
    @SequenceGenerator(sequenceName = "p_sessions_s", name = "p_sessions", allocationSize = 1, initialValue = 1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "p_sessions")
    public long getId() {
        return this.id;
    }
 
    public void setId(long id) {
        this.id = id;
    }
 
    @Column(name = "status", nullable = false, length = 1)
    public char getStatus() {
        return this.status;
    }
 
    public void setStatus(char status) {
        this.status = status;
    }
 
    @Column(name = "language", length = 2)
    public String getLanguage() {
        return this.language;
    }
 
    public void setLanguage(String language) {
        this.language = language;
    }
 
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "expire_date", length = 29)
    public Date getExpireDate() {
        return this.expireDate;
    }
 
    public void setExpireDate(Date expireDate) {
        this.expireDate = expireDate;
    }
 
    @Column(name = "expire_sessions", precision = 3, scale = 0)
    public Short getExpireSessions() {
        return this.expireSessions;
    }
 
    public void setExpireSessions(Short expireSessions) {
        this.expireSessions = expireSessions;
    }
 
    @Column(name = "max_sessions", precision = 3, scale = 0)
    public Short getMaxSessions() {
        return this.maxSessions;
    }
 
    public void setMaxSessions(Short maxSessions) {
        this.maxSessions = maxSessions;
    }
 
    @Column(name = "session_timeout", precision = 6, scale = 0)
    public Integer getSessionTimeout() {
        return this.sessionTimeout;
    }
 
    public void setSessionTimeout(Integer sessionTimeout) {
        this.sessionTimeout = sessionTimeout;
    }
 
    @Column(name = "max_session_durat", precision = 8, scale = 0)
    public Integer getMaxSessionDurat() {
        return this.maxSessionDurat;
    }
 
    public void setMaxSessionDurat(Integer maxSessionDurat) {
        this.maxSessionDurat = maxSessionDurat;
    }
 
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "last_login", length = 29)
    public Date getLastLogin() {
        return this.lastLogin;
    }
 
    public void setLastLogin(Date lastLogin) {
        this.lastLogin = lastLogin;
    }
 
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "last_wrong_login", length = 29)
    public Date getLastWrongLogin() {
        return this.lastWrongLogin;
    }
 
    public void setLastWrongLogin(Date lastWrongLogin) {
        this.lastWrongLogin = lastWrongLogin;
    }
 
    @Column(name = "login", unique = true, nullable = false, length = 50)
    public String getLogin() {
        return this.login;
    }
 
    public void setLogin(String login) {
        this.login = login;
    }
 
    @Column(name = "password")
    public byte[] getPassword() {
        return this.password;
    }
 
    public void setPassword(byte[] password) {
        this.password = password;
    }
 
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "PUser")
    public Set<PSession> getPSessions() {
        return this.pSessions;
    }
 
    public void setPSessions(Set<PSession> pSessions) {
        this.pSessions = pSessions;
    }
 
}

Transakcyjność

Konektor typu Hibernate może być dołączany do transakcji realizowanej za pomocą standardowego mechanizmu jPALIO.

Przykład w języku Groovy:

Sql sql = Groovy.module("sql")
try {
  sql.transactionStart()
  sql.transactionAdd("connector_name", "data")
  ..
  // Hibernate session operations
  // Operations sql.write...
  ..
  sql.commit()
} catch(Exception ex) {
  sql.rollback()
} finally {
  sql.transactionStop()
}

 

DAO i Hibernate

Klasa palio.connectors.hibernate.GenericHibernateDAO pozwala na łatwe tworzenie obiektów DAO (Data Access Object) przy użyciu Hibernate.

Skrócona definicja klasy:

public abstract class GenericHibernateDAO<T, ID extends Serializable> implements GenericDAO<T, ID> {
  public T getById(ID id, boolean lock)
  public T getById(ID id, boolean lock)
  public List<T> getAll()
  public List<T> getByExample(T exampleInstance, String... excludeProperty)
  public T persist(T entity)
  public boolean remove(T entity)
  protected List<T> findByCriteria(Collection<Criterion> criterion)
  protected List<T> findByCriteria(Collection<Criterion> criterion, String orderColumn, boolean ascOrder, Integer offset, Integer count)
  protected Session getSession()
}


Aby stworzyć klasę DAO dla konkretnego obiektu należy stworzyć nową klasę dziedziczącą po klasie GenericHibernateDAO.

Przykładowa implementacja:

package palio.dao;
 
import palio.*;
import palio.model.*;
import palio.connectors.hibernate.*;
 
public class PUserDAO extends GenericHibernateDAO<PUser, Long> {
 
    private static final String CONNECTOR_NAME = "connector_name"
 
    public static PUserDAO getInstance() throws PalioException {
        PUserDAO dao = new PUserDAO();
        dao.setSession(Groovy.module("hibernate").getCurrentSession(CONNECTOR_NAME));
        return dao;
    }
 
}


Przykładowe użycie:

PUserDAO userDao = PUserDAO.getInstance()
List<PUser> pUsers = userDao.getAll()
for
(pUser in pUsers) { println "user: ${pUser.getLogin()}" }

Dobre praktyki

Usuwanie obiektów

Złe podejście (wywołanie obiektu DAO):

def object = dao.getById(1);
dao.remove(object);


Dobre rozwiązanie (ciało metody wewnątrz obiektu DAO):

def session = getSession();

String hql = "delete from sample where id = :target";

Query query = session.createQuery(hql);
query.setParameter("target", 1);
query.executeUpdate(); 


Wniosek: W złym podejściu niepotrzebnie pobieramy cały obiekt, skoro tak naprawdę potrzebujemy tylko jego identyfikator do usunięcia. 



Pobieranie listy obiektów z bazy danych

W zależności od typu obiektu przechowywanego w bazie danych programista powinien zastanowić się nad zaimplementowaniem wydajnej metody do pobierania listy obiektów. Nie ma najmniejszego problemu, jeżeli obiekt to na przykład profil użytkownika składający się z trzech właściwości: imię, nazwisko i e-mail. Problem pojawia się wtedy kiedy profil ten zawiera także obrazek zapisany jako tablica bajtów. Przy wywołaniu metody dao.getAll() z bazy pobrane zostaną wszystkie obrazki, to znacznie spowolni działanie aplikacji. Te tablice bajtów w większości przypadków nie są potrzebne w warstwie prezentacji.

Przykład prawidłowej implementacji metody getAll:

def session = getSession();

String hql = "select firstname, lastname, email from Profile";

Query query = session.createQuery(hql);

return query.list();


Uwaga: W zapytaniu HQL używamy nazw właściwości obiektu a nie nazw kolumn w bazie danych. Podobnie postępujemy z klasami i nazwami tabel.