Encje i cały ten bałagan

Przyszedł czas by rozpocząć zabawę w certyfikację JPA. W dzisiejszym wpisie zamierzam przedstawić pokrótce podstawowe pojęcia i zagadnienia dotyczące mapowania ORM, encji i o co w ogóle chodzi z tymi pojęciami.

Encje

Podstawowym pojęciem w przypadku mapowania relacyjno-obiektowego oraz persystencji jest encja. Z pewnością spotkaliście się z tym pojęciem w wielu różnych znaczeniach. Nasza książeczka definicuje ją krótko i treściwie:

An entity is essentially a noun, or a grouping of state associated together as a single unit. It may participate in relationships to any number of other entities in a number of standard ways. In the object-oriented paradigm, we would add behavior to it and call it an object.

Jest to bardzo krótka i treściwa charakterystyka. Nasuwającym się pytaniem jest – jakie to cechy musi posiadać obiekt, aby uznać go za encję? Jest ich kilka:

  • Persistability – encje jako takie muszą mieć możliwość być zapisanymi w bazie danych.
  • Identity – obiekt, który chcemy uznać za encję musi być jednoznacznie identyfikowalny pomiędzy innymi podobnymi sobie obiektami. Jeśli bowiem mamy dokonywać operacji na obiekcie musimy mieć pewność, że dokonujemy ich na wybranym przez nas obiekcie, a nie pierwszym lepszym, który się nawinął. Dla nas oznacza to, że obiekt posiada unikalny klucz.
  • Transactionality – encje muszą być ‘transakcyjne’ (mam nadzieję, że to prawidłowe tłumaczenie) – móc istnieć w kontekście transakcji. Do transakcji wrócę później.
  • Granularity – słowo to można przetłumaczyć jako ‘ziarnistość’. Chodzi o to, aby encja była pełnoprawnym obiektem, który trzyma w sobie nie pojedynczą wartośc (o prymitywie nie wspomnę), ale kilkanaście cech/obiektów, które tworzą logiczną całość. Tworzenie encji do trzymania ID i jednego łańcucha tekstowego jest możliwe, ale raczej mało (najczęściej) sensowne.

Encja jako taka poza informacjami, które przechowuje ‘sama w sobie’ (czyli po prostu swoimi własnościami) może posiadać tzw. metadane. Opisują one w jaki sposób encja ma się zachować w środowisku JPA. Zasadniczo jak większość tego typu danych w Javie możemy zapisać je na dwa sposoby – adnotacjami bądź też za pomocą XMLa. Wszystkie adnotacje, o których tutaj mowa znajdują się w pakiecie javax.persistence.*. Plików XML obecnie głównie używa się do stworzenia pliku konfiguracyjnego dla JPApersistence.xml, który opisuje w jaki sposób będą zachowywały się nasze encje oraz dokładniejsze dane konfiguracyjne (najczęściej specyficzne dla dostawcy JPA). Oczywiście nic nie stoi na przeszkodzie, aby za ich pomocą również mapować encje. Jednakże od takiego podejścia się odchodzi (generuje duże ilości XMLa i rozbija informacje będące dla encji istotne na 2 pliki). W tutorialach oczywiście będę posługiwał się tylko adnotacjami (dla czystości kodu przede wszystkim).

Ok. Wiemy jakie cechy powinna mieć encja. Nie zbliżyliśmy się jednakże choćby odrobinę do stworzenia takowej z obiektu. Do tego zasadniczo potrzeba dwóch rzeczy:

  • adnotacji @Entity – adnotacja markerowa używana na poziomie klasy
  • adnotacji @Id – adnotacja markerowa używana na poziomie własności obiektu (lub metody dostępowej – o tym później).

Trochę wyprzedzając temat – brak adnotacji @Entity przy wpisaniu klasy jako potencjalnej encji w pliku konfiguracyjnym skutkuje błędem czasu wykonania (np. przy próbie zapisu encji). Brak adnotacji @Id z kolei zaowocuje wyjątkiem podczas wykonania (u mnie wyleciał org.hibernate.AnnotationException docelowo opakowanym w javax.persistence.PersistenceException). Zasadniczo wszystkie błędy niższego poziomu docelowo są wrapowane w PersistenceException.

Spójrzmy zatem na przykładową encję. Dodam znów na marginesie – modelem, którego będziemy używać jest gra RPG – będziemy zatem mieli encję reprezentującą gracza, jego cechy, posiadane przedmioty i co tam mi jeszcze do głowy przyjdzie. Zakładam, że będzie to troszkę bardziej przemawiające niż po raz milionowy przykłady typu User, Phone czy Address.

package com.wordpress.chlebik.jpa.domain;

import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import org.hibernate.annotations.GenericGenerator;

@Entity
@Table( name = "HEROES" )
public class Hero {

    @Id
    @GeneratedValue(generator="increment")
    @GenericGenerator(name="increment", strategy = "increment")
    private Long id;

    @Temporal(TemporalType.TIMESTAMP)
    private Date creationDate;

    private String name;
    private Integer level;

    public Hero() { }

    public Long getId() {
		return id;
    }

    private void setId(Long id) {
		this.id = id;
    }

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getLevel() {
		return level;
	}

	public void setLevel(Integer level) {
		this.level = level;
	}

	public Date getCreationDate() {
		return creationDate;
	}

	public void setCreationDate(Date creationDate) {
		this.creationDate = creationDate;
	}
}

Konkretnymi adnotacjami sugeruję na razie się nie przejmować (wrócę do tego). Jak widać nasza encja jest prostym POJOsem z dodatkami. Pytanie powinno brzmieć – gdzie i w jaki sposób taki obiekt zostanie zapisany w bazie danych? Zasada jest prosta – o ile nie jest ustawione inaczej wszystkie własności obiektu zostaną zapisane do bazy danych. Niezależnie od tego czy istnieją akcesory! Gdzie zapisane? Ano tutaj warto przyjrzeć się dokładniej adnotacji @Table – posiada ona atrybut o nazwie (jakże inaczej) – name. Wskazuje on na nazwę tabeli w bazie danych. Jeżeli atrybutu tego brak – wówczas w charakterze nazwy tabeli zostanie użyta nazwa encji.

EntityManager

Sama encja jest bardzo fajnym wynalazkiem. Zgódźmy się jednak – sama to ze sobą nic nie zrobi. Tutaj na scenę wkracza kolejne pojęcie-klucz – EntityManager. Jest to interfejs, który dostarcza funkcjonalności, dzięki którym w ogóle możemy mówić o operacjach bazodanowych (wyszukiwanie, zapisywanie i wszystko inne). Konkretne implementacje EntityManagera pobieramy korzystając z innego interfejsu o nazwie EntityManagerFactory. Zaś konkretne EntityManagerFactory są jednoznacznie powiązane z pojęciem PersistenceUnit. Jest to zdefiniowana w pliku persistence.xml ‘jednostka’, w której określamy klasy, które będą encjami w ramach PersistenceContext dostarczanego przez EntityManagera, a także właściwości połączenia do bazy danych. Rozumiecie? Pewnie że nie, ja też bym w tym momencie nie zrozumiał 😉

Poniżej mały rysunek.

Persistence UML

Zaś dalej sensownym do przeczytania wydaje się być materiał, do którego linkowałem już w pierwszym wpisie o JPA. EntityManagerFactory – obiekt bez którego ani rusz – w Javie SE otrzymujemy po prostu korzystając ze statycznej metody klasy Persistence. Kiedy mamy fabrykę wyciągnięcie EntityManagera jest trywialne:

EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory( "persistenceUnit" );
EntityManager entityManager = entityManagerFactory.createEntityManager();

Tadam! Mamy EntityManagera i możemy ruszać na podbój świata 😉 Poza podbojem możemy też zająć się bardziej przyziemnymi rzeczami:

  • zapisywaniem encji w bazie (INSERT) – w taki oto choćby sposób:
    Hero h = new Hero();
    h.setCreationDate(new Date());
    h.setLevel(1);
    h.setName("Chlebikowy Mag");
    entityManager.persist( h );
    
  • wyszukaniem encji w bazie (SELECT) – posiłkując się kluczem głównym oraz typem encji:
    Hero h = entityManager.find(Hero.class, 1L);
    
  • usuwaniem encji (DELETE) – z małą pomocą wyszukiwania:
    Hero h = entityManager.find(Hero.class, 1L);
    entityManager.remove(h);
    
  • zmianą encji (UPDATE) – o dziwo bez konieczności używania EntityManagera do przeprowadzenia aktualizacji:
    Hero h = entityManager.find(Hero.class, 1L);
    h.setName("Chlebikowy wojownik");
    

Proste prawda? Aż miło popatrzeć jak odwieczne ‘SELECT * FROM’ i insze klony odchodzą powoli do lamusa.

Transakcje i SQL

By zakończyć temat ciekawych zastępników odniosę się jeszcze do transakcji i zapytań. Zasadniczo omawiany przeze mnie przykład dotyczy JPA uruchamianego poza środowiskiem EE (np. z aplikacji ‘konsolowej’ czy w Tomcacie). Używanie JPA w ramach serwera aplikacyjnego będzie poruszane w odpowiednich momentach (w aplikacjach JEE EntityManagera po prostu wstrzykujemy używając adnotacji), jednakże dla nauki łatwiej jest posiłkować się najprostszą możliwą aplikacją (głównie chodzi o szybkość uruchamiania). Wspominam o tym dlatego, iż w przypadku takich aplikacji wszystkie operacje zmieniające dane w bazie wymagają ‘wrapowania’ w transakcję. By nie zaciemniać zagadnienia (do transakcji powrócę w następnych wpisach) kawałeczek kodu jak powinny wyglądać przedstawione powyżej operacje (CUD).

entityManager.getTransaction().begin();

Hero h = new Hero();
h.setCreationDate(new Date());
h.setLevel(1);
h.setName("Chlebikowy Mag");
entityManager.persist( h );

entityManager.getTransaction().commit();

Do tej pory używaliśmy metod EntityManagera by osiągnąć zamierzony cel. W codziennej praktyce programistycznej pojawiają się często sytuacje, w których samo find nie wystarczy. Co zrobić kiedy potrzebujemy wykonać troszeczkę bardziej skomplikowane zapytanie? Z pomocą przychodzą nam wówczas również metody EntityManagera, które pozwalają na tworzenie obiektów zapytań. Reprezentowane są one przez dwa podstawowe interfejsy – TypedQuery oraz Query. Przykład tego pierwszego poniżej:

TypedQuery query = entityManager.createQuery("SELECT h FROM Hero h", Hero.class);
List heroes = query.getResultList();

Plik konfiguracyjny persistence.xml

Do tej pory głównie skupiałem się na encjach oraz pobocznym kodzie. O konfiguracji samego JPA wspomniałem niejako mimochodem. Prawda jednak jest taka, iż istnienie pliku persistence.xml jest warunkiem koniecznym dla poprawnego działania całej powyższej magii. Plik ten znajduje się w folderze META-INF (w projektach Mavena folder ten powinien leżeć w /src/main/resources). Mój dość podstawowy plik (z konfiguracją bazy danych H2 dla prostoty uruchamiania i działania) wygląda następująco:

<!--?xml version="1.0" encoding="UTF-8"?-->


 com.wordpress.chlebik.jpa.domain.Hero





 

Najistotniejszymi elementami jest nazwa naszego persistence-unit (używa się jej do stworzenia instancji EntityManagerFactory), lista klas encyjnych oraz podstawowe własności dla połączenia. Przed specyfikacją JPA w wersji 2 nazwy własności bywały różne (w zależności od bazy danych), co powodowało zrozumiałe problemy. Obecnie gdzie tylko to możliwe nazwy te są zestandaryzowane jak tylko się da.

Advertisements

2 thoughts on “Encje i cały ten bałagan

  1. Alek

    Witam,
    zamiast: @Entity – posiada ona atrybut o nazwie (jakże inaczej) – name.
    powinno być: @Table – posiada ona atrybut o nazwie (jakże inaczej) – name

    Pozdrawiam
    Alek

    P.S.
    Dobrze się zaczyna, powodzenia i wytrwałości 🙂

  2. chlebik Post author

    Faktycznie, tak to jest jak się z rozpędu pisze. Dzięki za zwrócenie uwagi i za miłe słowa.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s