#13.2 – Baza danych z JPA cz. 2

By 22 listopada 2014Kurs Javy
Wpis-Header

W poprzedniej części dowiedziałaś się, czym jest JPA oraz jak używać adnotacji związanych z JPA, dzisiaj zobaczymy jak skonfigurować go w projekcie :)

Jak zaraz sama zobaczysz, konfiguracja jest trywialna i sprowadza się do fragmentu XML i dodania odpowiednich bibliotek. Zobaczymy także jak korzystać z EntityManagera aby pobrać interesujące nas obiekty.

Lekcja

Dodajemy biblioteki do projektu

Aby korzystać z JPA w naszym projekcie musimy dodać odpowiednie zależności. W tym przypadku będziemy korzystać z implementacji Hibernate, więc to ją dołączymy do naszej aplikacji. Będziemy potrzebować poniższych zależności w module model (zakładając, że to tam będą też DAO):

Konfiguracja Springa

W naszym pliku XML z konfiguracją Springa dodajemy 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 pierwszy bean, który określa nam zachowanie EntityManagera. Pozostałe beany są ‚wspierające’, tzn. pełnią określone funkcje (np. zwracają sensowne wyjątki w niektórych przypadkach czy zarządzają tworzeniem i zamykaniem transakcji), ale nie wymagają dodatkowej konfiguracji.

Jeśli chodzi o pierwszy bean jest to implementacja wzorca fabryki – czyli klasa, która posiada metodę pozwalającą w automatyczny sposób tworzyć instancje obiektów. To pobieranie elementu odbywa się automatycznie bez naszego udziału za pomocą adnotacji (o tym, jak to zrobić, dowiesz się w kolejnym punkcie). Poszczególne parametry są następujące:

  • dataSource – to odniesienie do beana, który definiuje nasz DataSource (dodawałaś go w lekcji, w której uczyliśmy się obsługiwać bazę danych)
  • packagesToScan – jest to początek nazw pakietów, w których mamy swoje encje
  • jpaVendoAdapter – to z kolei bean (w naszym wypadku, możemy też zdefiniować go oddzielnie i użyć tylko atrybutu ref żeby wskazać jego nazwę). Bean ten ma dwa parametry dodatkowe:
    • showSQL – steruje tym, czy na konsole mają być wypisywane zapytania SQL, które trafiają do bazy danych. Jest to przydatne przy rozwijaniu aplikacji (możemy łatwo zobaczyć, czy operacja, która powinna się wykonać, faktycznie została wykonana)
    • databasePlatform – wskazuje nazwę klasy, która opisuje naszą bazę danych. Ponieważ różne bazy danych (np. MySQL, PostgreSQL, Oracle) w różny sposób rozszerzają standardową składnię języka SQL, Hibernate stara się wykorzystywać te różnice, żeby zoptymalizować czy przyspieszyć pewne rzeczy. W tym miejscu możemy więc wskazać z jakiej bazy danych korzystamy, aby ‚pomóc’ bibliotece Hibernate zoptymalizować swoje działania
  • jpaProperties – tutaj możemy zdefiniować wszystkie niestandardowe ustawienia, np. specyficzne dla naszej implementacji, w naszym przypadku wykorzystamy przede wszystkim jedną opcję:
    • hibernate.hbm2ddl.auto – określa ona, jak hibernate ma się zachowywać przy uruchomieniu. Dostępne jest kilka opcji:
      • validate – tylko weryfikuje, tej opcji powinniśmy używać w działającej aplikacji, jeśli pojawi się jakaś niespójność, aplikacja się nie uruchomi
      • update – Hibernate w przypadku natrafienia na niespójność spróbuje ją usunąć poprzez modyfikację schematu; narażamy się przez to na utratę danych, opcja nadaje się głównie do testowania i rozwoju
      • create – tworzy schemat i strukturę, usuwając istniejące dane
      • create-drop – podobnie jak create tworzy schemat i strukturę usuwając istniejące dane, ale przy zamykaniu aplikacji automatycznie usuwa całą zawartość używanej bazy danych

@PersistenceContext – EntityManager w naszej aplikacji

Aby korzystać z EntityManagera w naszych beanach należy skorzystać z adnotacji @PersistenceContext, przykładowa klasa wygląda następująco:

public class KotDao {

    @PersistenceContext
    EntityManager entityManager;

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

Dodanie pola typu EntityManager oraz dodając nad nim adnotację @PersistenceContext to wszystko, co musimy zrobić aby móc używać go w naszej aplikacji. Dodatkowo, każda metoda, która korzysta z EntityManagera msui mieć adnotację @Transactional – jest to ważne, ponieważ pozwala nam to ‚powiedzieć’ bibliotece Hibernate kiedy jest początek i kiedy koniec transakcji, dzięki czemu nie będziemy mieli problemów z tym, że pewne zmiany nie będą widoczne lub będą powodowały problemy.

Korzystamy z EntityManagera

Poznamy teraz, w jaki sposób używać EntityManagera do najpopularniejszych operacji. W tym miejscu gorąco zachęcam, żeby zapoznać się z dokumentacją EntityManagera i jego metod – dokumentacja Javy jest naprawdę dobra i często pozwala uporządkować sobie wiedzę na temat danego wycinka funkcjonalności.

metoda find

Metoda find służy do pobierania jednego obiektu na podstawie jego klucza. Kluczem do wyszukania obiektu jest to pole, które posiada adnotację @Id. Przykładowe wywołanie:

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

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

Jeśli metoda ta nie znajdzie danego obiektu w bazie danych, zwraca wartość null.

metoda merge

Metody tej można używać zarówno do wstawiania obiektu do bazy danych jak i jego aktualizacji. W przypadku tej pierwszej operacji można użyć także metody persist() – parę słów o różnicach poniżej. Przykładowe wywołanie:

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

utworzy nam nowy rekord kota w bazie danych. Jak zauważyłaś, metoda merge zwraca nam kopię obiektu – obiekt zwrócony przez tą metodę jest zarządzany, tzn. wszelkie zmiany, jakie na nim wykonany (w ramach jednej metody z adnotacją @Transactional lub metod, które taka metoda wywołuje) od razu będą utrwalone w bazie danych. Z tej funkcjonalności korzysta się względnie sporadycznie, warto jednak wiedzieć (jeśli pojawią się w bazie danych zmiany, których nie zapisywaliśmy bezpośrednio).

merge vs persist

Różnica pomiędzy tymi metodami jest taka, że metoda merge zaktualizuje obiekt w bazie danych, jeśli ma takie samo id. Najczęściej tworząc nowe obiekty, nie przypisujemy im id (pozwalamy, żeby baza danych je sama utworzyła). W takiej sytuacji można bezpiecznie używać merge zarówno do tworzenia jak i aktualizacji obiektów. Jeśli jednak chcemy być w 100% poprawni, powinniśmy użyć metody persist.

Różnica jest też w specyficznym zachowaniu dot. zarządzania encją (patrz wyżej) – merge tworzy kopię obiektu, i to ta kopia jest zarządzana. Weźmy wiec pod uwagę następującą sytuacje:

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

Załóżmy, że metoda zapiszKota dodaje kota do bazy danych z użyciem metody persist lub merge.

Jeśli używamy metody persist, imię kota w bazie danych po zakończeniu tej metody będzie „tymczasowe imie” – metoda ta powoduje, że sam obiekt jest zarządzany (więc jeśli mieliśmy referencje do niego w innym miejscu, te zmiany też mogą trafić do bazy danych).

Jeśli zaś używamy 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 jednak mieć ich świadomość – w specyficznych sytuacjach ta wiedza może Ci się przydać. Nie ma też jednoznacznie dobrego/złego podejścia. Osobiście preferuje używać tylko merge (dzięki czemu jeśli używam zarządzanej instancji obiektu, to robię to świadomie i celowo), ale jest to kwestia konwencji w projekcie i własnych preferencji.

JPQL i zapytania

JPQL to skrót od Java Persistence Query Language. Język ten oferuje mnóstwo możliwości, podobnie do języka SQL. Naszym celem nie jest poznać go dokładnie (możnaby o tym napisać cały kurs), ale jedynie wiedzieć, jak go używać i gdzie szukać dalszych informacji :) To co warto też wiedzieć, to to, że istnieje także HQL, który jest rozszerzeniem JPQL obsługiwanym przez bibliotekę Hibernate. W przypadku podstawowych operacji, możemy te pojęcia utożsamiać (mając jednak świadomość, że są to inne rzeczy!)

Zapytań możemy użyć np. do pobrania listy obiektów.

Aby wykonać zapytanie (bez parametrów, o nich powiemy sobie w przyszłości) musimy najpierw je utworzyć za pomocą metody entityManager.createQuery a następnie pobrać wynik. Aby pobrać listę kotów potrzebujemy wiec wykonać poniższy kod:

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

Jeśli nasze zapytanie zwraca tylko jeden element, możemy użyć metody query.getSingleResult();

Uwaga! Ważne jest, aby sprawdzać, co zostało zwrócone i zabezpieczyć się na wypadek tego, że nie ma w bazie danych rekordów, które spełniają nasze kryteria. W przypadku zapytań JPQL, jeśli zapytanie do bazy danych nie zwróci wyników (wynik jest pusty), to:

  • metoda getResultList() zwróci wartość null, a nie pustą listę!
  • metoda getSingleResult() wyrzuci wyjątek (NoResultException)! (w sytuacji, kiedy mamy więcej niż jeden wynik, także otrzymamy wyjątek – NonUniqueResultException)
JPQL vs HQL

HQL to akronim od Hibernate Query Language – jest to rozszerzenie JPQLa, które jest implementowane przez bibliotekę Hibernate. W praktyce przy podstawowych zapytaniach HQL jest właściwie identyczny do JPQL (w każdym razie jest w 100% zgodny z JPQLem).

Zadanie

Zmodyfikuj program, który już napisałeś tak, aby korzystał z JPA i Entity Managera w klasie DAO.

Używając hibernate ustaw parametr hbm2ddl na wartość ‚update’, dzięki czemu struktura bazy danych będzie automatycznie tworzona, jeśli nie będzie poprawna.

Licencja Creative Commons

Jeśli uważasz powyższą lekcję za przydatną, mamy małą prośbę: polub nasz fanpage. Dzięki temu będziesz zawsze na bieżąco z nowymi treściami na blogu ( i oczywiście, z nowymi częściami kursu Javy). Dzięki!

  •  
  •  
  •  
  •  
  •  
  • Arek

    Hej ! mam nadzieje że nie zostanę zlinczowany jeśli okaże się że zadaje głupie pytania ;) Jestem całkiem nowy jeśli chodzi o springa. Chciałem sobie zrobić połączenie do bazy danych bazując na waszym kursie jednak dostaje nulla na EntityManager :/
    Zadałem pytanie na stackOverflow, czy ktoś mógłby na nie zerknąć, i dać mi wskazówkę na to co robię źle ?

    http://stackoverflow.com/questions/28142656/entitymanager-using-spring-null-pointer-exception

    • Cześć!
      Na StackOverflow ktoś Ci już odpowiedział ;) Ogólnie chodzi o to, że wszystkie adnotacje mają znaczenie tylko, jeśli to Spring tworzy Twój komponent (używa do tego specjalnej klasy, tzw. bewn post processor).
      Najprostszy sposób, żeby uruchomić Twój kod, to dodać Adnotację @Component nad UserDao a następnie zamiast UserDao dao = new UserDao(); zrobić @Autowires UserDao dao;

      Gdybyś miał problem lub coś nadal by nie działało, dawaj znać :)

  • Cześć,

    należy się tutaj zastanowić, jak działa merge – w najprostszym wariancie sprawdza on ID, i jeśli nie jest nullem, to pobiera najpierw z bazy danych ten rekord. Stąd zapytanie SELECT w konsoli :)

    Spróbuj dodać do tej kolumny id adnotację @GeneratedValue(strategy = GenerationType.AUTO) – mówi ona o tym, w jaki sposób są nadawane numery ID. Do zapisywania encji (jeśli sam z palca ustawiasz ID) używaj raczej persist :)

    Mam nadzieję, że to pomoże, daj znać proszę czy udało Ci się rozwiązać problem!

    Pozdrawiam, Jakub Derda

  • Łukasz Kołakowski

    Czemu od paru lekcji nie ma już rozwiązań zadań do ściągnięcia :( ?

    • Cześć!
      Robimy co w naszej mocy by uzupełnić zadania na blogu o rozwiązania, a trwa to tyle z bardzo prozaicznej przyczyny – oboje pracujemy na pełen etat i blogowe obowiązki są dla nas zajęciem dodatkowym, na które nie zawsze jesteśmy w stanie poświęcić odpowiednią ilość czasu. Zwróć uwagę, że szybciej idzie nam odpowiadanie na komentarze więc w razie wątpliwości czy pytań polecamy napisać, bo szybko odpowiemy. Możemy też obiecać, że co tydzień sukcesywnie będziemy dodawać rozwiązania, jednak, we’re only humans ;)

      • Łukasz Kołakowski

        Trzymam mocno kciuki za dalszy rozwój serwisu, bo kurs naprawdę świetny :). Może pomyślcie o jakimś nowym członku drużyny.

  • Łukasz Kołakowski

    Mam problem. Wyszukiwanie zapytań mi działa, ale zapisywanie czegoś do tabeli już nie. Próbowałem persist oraz merge i nic. :(

  • Łukasz Kołakowski

    Już wiem, trzeba w applicationContext.xml dodać . Bez tego nie da się nic zapisywać do bazy danych.

    • Faktycznie, aby transakcje działały, wymagane jest dodanie do konfiguracji fragmentu:

  • Kuba

    Hej, już pytałem o podobną sprawę przy serwisach: jestem chory na perfekcjonizm i bardzo chciałbym wiedzieć, jak zorganizować projekt w Mavenie, konkretnie: do jakiego modułu należą Koty, a do jakiego KotyDAO? I czym jest tajemniczy moduł koty-domain który macie w rozwiązaniach poprzednich zadań?
    A poza tym, to dzięki Wam za świetną robotę którą robicie :)

    • W naszym przypadku Kot należy do modułu -model, DAO w zależności od kontekstu może być w -model lub -persistence – tak jak pisaliśmy w poprzedniej odpowiedzi na Twoje pytanie, nie ma tutaj jednego standardu. Wiele zależy od tego, jak pracujecie w danym projekcie i jakie są preferencje zespołu.
      koty-domain to moduł na klasy modelu – model aplikacji bywa też nazywany jego domeną.

  • Cześć, wygląda jakbyś miał w projekcie biblioteki w różnych wersjach – możesz podesłać całą paczkę z projektem? Ew zerknij do pom’ów, czy nie masz przypadkiem np. Springa czy Hibernate w różnych wersjach w kilku miejscach.

    • Paweł Kalbarczyk

      Tutaj mój projekt bazujący na aplikacji którą macie tutaj w tutorialu. Sprawdziłem wersje – wydają się w porządku. Nie wiem, może brakuje mi jakiejś zależności. Podsyłam spakowany projekt, poniżej link:
      https://www.sendspace.com/file/fbiq1r

      • Adam527

        I wiadomo jakie jest rozwiązanie tego problemu?

        • Cześć, w przesłanym kodzie brakuje zależności do JTA (Java transaction API), wystarzy dodać poniższy fragment do pom.xml, aby błąd ten przestał występować:

          javax.transaction
          jta
          1.1

          • Adam527

            Teraz mam taki błąd i nigdzie nie mogę znaleźć rozwiązania:
            java.lang.NoSuchMethodError: org.springframework.beans.factory.annotation.InjectionMetadata.needsRefresh(Lorg/springframework/beans/factory/annotation/InjectionMetadata;Ljava/lang/Class;)Z

        • Paweł Kalbarczyk

          Tak, znalazłem rozwiązanie problemu. Trzeba dodać do projektu zależność JTA z pakietu javax.transaction. Po dodaniu tej zależności aplikacja u mnie ruszyła. Znalazłem to rozwiązanie na stackoverflow.

  • OK, postaramy się Wam pomóc w w tym tygodniu przejrzymy Wasz kod :)

  • Mariusz

    Przydałoby się gdzieś napisać jaka jest różnica między JPQLem a HQLem.
    pozdrawiam

    • Zaktualizowane ;) Na poziomie omawianym na tym kursie, oba ‚języki’ można właściwie utożsamiać, różnice są bardziej widoczne przy bardziej skomplikowanych zapytaniach. Dzięki za zwrócenie uwagi!

  • kkk2016

    Czesc, dodałam zaleznosci do modułów i zmodyfikowalam tylko jedną klase w projekcie (klase DAO). Czy ten kod jest poprawny? Jesli tak, to jak mam teraz uruchomic cala aplikacje, zeby sprawdzic czy baza danych dziala? Gdzie mam wpisac zapytania?

    @Repository
    public class KotDAO {

    @PersistenceContext
    EntityManager entityManager;

    @Transactional
    public Kot getKotById(int id) {
    Kot kot = new Kot();
    kot = entityManager.find(Kot.class, id);
    return kot;
    }

    @Transactional
    public List getKoty() {
    Query query = entityManager.createQuery(„SELECT * FROM Kot”);
    List kk = new ArrayList();
    kk = query.getResultList();
    return kk;
    }

    @Transactional
    public void addKot(Kot kot) {
    kot.setImie(kot.getImie());
    kot.setData(kot.getData());
    kot.setWaga(kot.getWaga());
    kot.setWlasciciel(kot.getWlasciciel());
    kot = entityManager.merge(kot);
    }

    • kkk2016

      Powinno byc List ;). Pisałam na szybko.

      • kkk2016

        z duzej litery, ale nie moge jej wbic.
        Spamuje Wam… Pozdrawiam :)

  • Gosc

    Hej, czym jest tag tx w konfiguracji xmlowej?