#13.2 — Baza danych z JPA cz. 2

By 22 November 2014 June 30th, 2016 Kurs Javy

W poprzed­niej częś­ci dowiedzi­ałaś się, czym jest JPA oraz jak uży­wać adno­tacji związanych z JPA, dzisi­aj zobaczymy jak skon­fig­urować go w projekcie :)

Jak zaraz sama zobaczysz, kon­fig­u­rac­ja jest try­wial­na i sprowadza się do frag­men­tu XML i doda­nia odpowied­nich bib­liotek. Zobaczymy także jak korzys­tać z Enti­ty­Man­agera aby pobrać intere­su­jące nas obiekty.

Lekcja

Dodajemy biblioteki do projektu

Aby korzys­tać z JPA w naszym pro­jek­cie musimy dodać odpowied­nie zależnoś­ci. W tym przy­pad­ku będziemy korzys­tać z imple­men­tacji Hiber­nate, więc to ją dołączymy do naszej aplikacji. Będziemy potrze­bować poniższych zależnoś­ci w mod­ule mod­el (zakłada­jąc, że to tam będą też DAO):

Konfiguracja Springa

W naszym pliku XML z kon­fig­u­racją Springa doda­je­my poniższy fragment:

<tx:annotation-driven />
<bean id="entityManagerFactory"
 class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="packagesToScan" value="pl.kobietydokodu.model" />
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
            <property name="showSql" value="false" />
            <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
        </bean>
    </property>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.hbm2ddl.auto">update</prop>
        </props>
    </property>
</bean>
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

Najważniejszy jest pier­wszy bean, który określa nam zachowanie Enti­ty­Man­agera. Pozostałe beany są ‘wspier­a­jące’, tzn. pełnią określone funkc­je (np. zwraca­ją sen­sowne wyjąt­ki w niek­tórych przy­pad­kach czy zarządza­ją tworze­niem i zamykaniem transakcji), ale nie wyma­ga­ją dodatkowej konfiguracji.

Jeśli chodzi o pier­wszy bean jest to imple­men­tac­ja wzor­ca fab­ry­ki — czyli klasa, która posi­a­da metodę pozwala­jącą w automaty­czny sposób tworzyć instanc­je obiek­tów. To pobieranie ele­men­tu odby­wa się automaty­cznie bez naszego udzi­ału za pomocą adno­tacji (o tym, jak to zro­bić, dowiesz się w kole­jnym punkcie). Poszczególne para­me­try są następujące:

  • data­Source — to odniesie­nie do beana, który defini­u­je nasz Data­Source (dodawałaś go w lekcji, w której uczyliśmy się obsługi­wać bazę danych)
  • pack­agesToScan — jest to początek nazw paki­etów, w których mamy swo­je encje
  • jpaVen­doAd­apter — to z kolei bean (w naszym wypad­ku, może­my też zdefin­iować go odd­ziel­nie i użyć tylko atry­bu­tu ref żeby wskazać jego nazwę). Bean ten ma dwa para­me­try dodatkowe: 
    • showSQL — steru­je tym, czy na kon­sole mają być wyp­isy­wane zapy­ta­nia SQL, które trafi­a­ją do bazy danych. Jest to przy­datne przy rozwi­ja­niu aplikacji (może­my łat­wo zobaczyć, czy oper­ac­ja, która powin­na się wykon­ać, fak­ty­cznie została wykonana)
    • data­base­Plat­form — wskazu­je nazwę klasy, która opisu­je naszą bazę danych. Ponieważ różne bazy danych (np. MySQL, Post­greSQL, Ora­cle) w różny sposób rozsz­erza­ją stan­dar­d­ową skład­nię języ­ka SQL, Hiber­nate stara się wyko­rzysty­wać te różnice, żeby zop­ty­mal­i­zować czy przyspieszyć pewne rzeczy. W tym miejs­cu może­my więc wskazać z jakiej bazy danych korzys­tamy, aby ‘pomóc’ bib­liotece Hiber­nate zop­ty­mal­i­zować swo­je działania
  • jpa­Prop­er­ties — tutaj może­my zdefin­iować wszys­tkie nie­s­tandar­d­owe ustaw­ienia, np. specy­ficzne dla naszej imple­men­tacji, w naszym przy­pad­ku wyko­rzys­tamy przede wszys­tkim jed­ną opcję: 
    • hibernate.hbm2ddl.auto — określa ona, jak hiber­nate ma się zachowywać przy uru­chomie­niu. Dostęp­ne jest kil­ka opcji: 
      • val­i­date — tylko wery­fiku­je, tej opcji powin­niśmy uży­wać w dzi­ała­jącej aplikacji, jeśli pojawi się jakaś niespójność, aplikac­ja się nie uruchomi
      • update — Hiber­nate w przy­pad­ku natrafienia na niespójność spróbu­je ją usunąć poprzez mody­fikację schematu; narażamy się przez to na utratę danych, opc­ja nada­je się głównie do testowa­nia i rozwoju
      • cre­ate — tworzy schemat i struk­turę, usuwa­jąc ist­niejące dane
      • cre­ate-drop — podob­nie jak cre­ate tworzy schemat i struk­turę usuwa­jąc ist­niejące dane, ale przy zamyka­niu aplikacji automaty­cznie usuwa całą zawartość uży­wanej bazy danych

@PersistenceContext — EntityManager w naszej aplikacji

Aby korzys­tać z Enti­ty­Man­agera w naszych beanach należy sko­rzys­tać z adno­tacji @PersistenceContext, przykład­owa klasa wyglą­da następująco:

public class KotDao {

    @PersistenceContext
    EntityManager entityManager;

    @Transactional
    public void zrobCos() {
        // jakieś operacje z EntityManagerem ...
    }
}

Dodanie pola typu Enti­ty­Man­ag­er oraz doda­jąc nad nim adno­tację @PersistenceContext to wszys­tko, co musimy zro­bić aby móc uży­wać go w naszej aplikacji. Dodatkowo, każ­da meto­da, która korzys­ta z Enti­ty­Man­agera msui mieć adno­tację @Transactional — jest to ważne, ponieważ pozwala nam to ‘powiedzieć’ bib­liotece Hiber­nate kiedy jest początek i kiedy koniec transakcji, dzię­ki czemu nie będziemy mieli prob­lemów z tym, że pewne zmi­any nie będą widoczne lub będą powodowały problemy.

Korzystamy z EntityManagera

Poz­namy ter­az, w jaki sposób uży­wać Enti­ty­Man­agera do najpop­u­larniejszych oper­acji. W tym miejs­cu gorą­co zachę­cam, żeby zapoz­nać się z doku­men­tacją Enti­ty­Man­agera i jego metod — doku­men­tac­ja Javy jest naprawdę dobra i częs­to pozwala uporząd­kować sobie wiedzę na tem­at danego wycin­ka funkcjonalności.

metoda find

Meto­da find służy do pobiera­nia jed­nego obiek­tu na pod­staw­ie jego klucza. Kluczem do wyszuka­nia obiek­tu jest to pole, które posi­a­da adno­tację @Id. Przykład­owe wywołanie:

entityManager.find(Kot.class, 1L);

pobierze nam obiekt typu Kot, który ma id o wartoś­ci 1.

Jeśli meto­da ta nie zna­jdzie danego obiek­tu w bazie danych, zwraca wartość null.

metoda merge

Metody tej moż­na uży­wać zarówno do wstaw­ia­nia obiek­tu do bazy danych jak i jego aktu­al­iza­cji. W przy­pad­ku tej pier­wszej oper­acji moż­na użyć także metody per­sist() — parę słów o różni­cach poniżej. Przykład­owe wywołanie:

Kot kot = new Kot();
//uzupełniamy dane kota
kot = entityManager.merge(kot);

utworzy nam nowy reko­rd kota w bazie danych. Jak zauważyłaś, meto­da merge zwraca nam kopię obiek­tu — obiekt zwró­cony przez tą metodę jest zarządzany, tzn. wszelkie zmi­any, jakie na nim wyko­nany (w ramach jed­nej metody z adno­tacją @Transactional lub metod, które taka meto­da wywołu­je) od razu będą utr­walone w bazie danych. Z tej funkcjon­al­noś­ci korzys­ta się względ­nie spo­rady­cznie, warto jed­nak wiedzieć (jeśli pojaw­ią się w bazie danych zmi­any, których nie zapisy­wal­iśmy bezpośrednio).

merge vs persist

Różni­ca pomiędzy tymi meto­da­mi jest taka, że meto­da merge zak­tu­al­izu­je obiekt w bazie danych, jeśli ma takie samo id. Najczęś­ciej tworząc nowe obiek­ty, nie przyp­isu­je­my im id (pozwalamy, żeby baza danych je sama utworzyła). W takiej sytu­acji moż­na bez­piecznie uży­wać merge zarówno do tworzenia jak i aktu­al­iza­cji obiek­tów. Jeśli jed­nak chce­my być w 100% poprawni, powin­niśmy użyć metody persist.

Różni­ca jest też w specy­ficznym zachowa­niu dot. zarządza­nia encją (patrz wyżej) — merge tworzy kopię obiek­tu, i to ta kopia jest zarządzana. Weźmy wiec pod uwagę następu­jącą sytuacje:

@Transactional
public void zrobCos(Kot kot) {
    kot.setImie("stałe imie");
    zapiszKota(kot);
    kot.setImie("tymczasowe imie");
}

Załóżmy, że meto­da zapiszKo­ta doda­je kota do bazy danych z uży­ciem metody per­sist lub merge.

Jeśli uży­wamy metody per­sist, imię kota w bazie danych po zakończe­niu tej metody będzie “tym­cza­sowe imie” — meto­da ta powodu­je, że sam obiekt jest zarządzany (więc jeśli mieliśmy ref­er­enc­je do niego w innym miejs­cu, te zmi­any też mogą trafić do bazy danych).

Jeśli zaś uży­wamy metody merge, wartość imienia w bazie danych dla tego kota będzie “stałe imie” — zarządzana będzie kopia obiektu.

Różnice te najczęś­ciej nie mają znaczenia, należy jed­nak mieć ich świado­mość — w specy­ficznych sytu­ac­jach ta wiedza może Ci się przy­dać. Nie ma też jed­noz­nacznie dobrego/złego pode­jś­cia. Oso­biś­cie prefer­u­je uży­wać tylko merge (dzię­ki czemu jeśli uży­wam zarządzanej instancji obiek­tu, to robię to świadomie i celowo), ale jest to kwes­t­ia kon­wencji w pro­jek­cie i włas­nych preferencji.

JPQL i zapytania

JPQL to skrót od Java Per­sis­tence Query Lan­guage. Język ten ofer­u­je mnóst­wo możli­woś­ci, podob­nie do języ­ka SQL. Naszym celem nie jest poz­nać go dokład­nie (możn­a­by o tym napisać cały kurs), ale jedynie wiedzieć, jak go uży­wać i gdzie szukać dal­szych infor­ma­cji :) To co warto też wiedzieć, to to, że ist­nieje także HQL, który jest rozsz­erze­niem JPQL obsługi­wanym przez bib­liotekę Hiber­nate. W przy­pad­ku pod­sta­wowych oper­acji, może­my te poję­cia utożsami­ać (mając jed­nak świado­mość, że są to inne rzeczy!)

Zapy­tań może­my użyć np. do pobra­nia listy obiektów.

Aby wykon­ać zapy­tanie (bez para­metrów, o nich powiemy sobie w przyszłoś­ci) musimy najpierw je utworzyć za pomocą metody entityManager.createQuery a następ­nie pobrać wynik. Aby pobrać listę kotów potrze­bu­je­my wiec wykon­ać poniższy kod:

Query query = entityManager.createQuery("SELECT k FROM Kot k");
List<Kot> koty = query.getResultList();

Jeśli nasze zapy­tanie zwraca tylko jeden ele­ment, może­my użyć metody query.getSingleResult();

Uwa­ga! Ważne jest, aby sprawdzać, co zostało zwró­cone i zabez­pieczyć się na wypadek tego, że nie ma w bazie danych reko­rdów, które speł­ni­a­ją nasze kry­te­ria. W przy­pad­ku zapy­tań JPQL, jeśli zapy­tanie do bazy danych nie zwró­ci wyników (wynik jest pusty), to:

  • meto­da getRe­sultList() zwró­ci wartość null, a nie pustą listę!
  • meto­da getS­in­gleRe­sult() wyrzu­ci wyjątek (NoRe­sul­tEx­cep­tion)! (w sytu­acji, kiedy mamy więcej niż jeden wynik, także otrzy­mamy wyjątek — NonUniqueResultException)
JPQL vs HQL

HQL to akro­n­im od Hiber­nate Query Lan­guage — jest to rozsz­erze­nie JPQLa, które jest imple­men­towane przez bib­liotekę Hiber­nate. W prak­tyce przy pod­sta­wowych zapy­ta­ni­ach HQL jest właś­ci­wie iden­ty­czny do JPQL (w każdym razie jest w 100% zgod­ny z JPQLem).

Zadanie

Zmody­fikuj pro­gram, który już napisałeś tak, aby korzys­tał z JPA i Enti­ty Man­agera w klasie DAO.

Uży­wa­jąc hiber­nate ustaw para­metr hbm2ddl na wartość ‘update’, dzię­ki czemu struk­tu­ra bazy danych będzie automaty­cznie twor­zona, jeśli nie będzie poprawna.

Licencja Creative Commons

Jeśli uważasz powyższą lekcję za przy­dat­ną, mamy małą prośbę: pol­ub nasz fan­page. Dzię­ki temu będziesz zawsze na bieżą­co z nowy­mi treś­ci­a­mi na blogu ( i oczy­wiś­cie, z nowy­mi częś­ci­a­mi kur­su Javy). Dzięki!