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
- hibernate.hbm2ddl.auto — określa ona, jak hibernate ma się zachowywać przy uruchomieniu. Dostępne jest kilka opcji:
@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).
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!