Odpytywanie jednostek JPA z JPQL i natywnym SQL
Przykładowa aplikacja
fragmenty kodu omówione w artykule pochodzą z plików źródłowych Java używanych w przykładowej aplikacji dołączonej do artykułu. Przeglądając przykładowe archiwum, można zauważyć, że jest to prosta aplikacja internetowa oparta na technologiach Java Servlet i Java Persistence API. Dla uproszczenia, nie używa enterprise beans, wysyłając zapytania JPQL bezpośrednio z serwletów. Nie oznacza to jednak, że nie będziesz mógł korzystać z zapytań JPQL omówionych tutaj w enterprise beans—możesz zdefiniować zapytania JPQL w dowolnych komponentach Java EE.
Rysunek 1 ilustruje przykładową strukturę jednostek. Jak widać, zawiera zestaw podmiotów powiązanych ze sobą relacjami różnych typów. Taka rozgałęziająca się struktura jest potrzebna do zilustrowania użycia zapytań JPQL join omówionych w sekcji Definiowanie JPQL Joins w dalszej części artykułu.
Rysunek 1 relacje między jednostkami wykorzystywanymi w przykładowej aplikacji
szczegółowe instrukcje dotyczące konfiguracji, a następnie uruchomienia przykładowej aplikacji można znaleźć w readme.plik txt w katalogu głównym przykładowego archiwum.
używanie JPQL w aplikacjach Java EE
jeśli masz jakieś praktyczne doświadczenie z bazami danych, najprawdopodobniej masz już mokre stopy z SQL, standardowym narzędziem oferującym zestaw instrukcji do uzyskiwania dostępu do informacji i manipulowania nimi w relacyjnych bazach danych. W rzeczywistości istnieje wiele podobieństw między JPQL i SQL. Oba są używane do uzyskiwania dostępu i manipulowania danymi bazy danych, na dłuższą metę. I oba używają instrukcji nieprocesowych-poleceń rozpoznawanych przez specjalny interpreter. Co więcej, JPQL jest podobny do SQL w swojej składni.
główna różnica między JPQL i SQL polega na tym, że pierwszy zajmuje się encjami JPA, podczas gdy drugi zajmuje się bezpośrednio danymi relacyjnymi. Jako programista Java Możesz również dowiedzieć się, że używanie JPQL, w przeciwieństwie do SQL/JDBC, eliminuje potrzebę używania JDBC API z kodu Java—kontener wykonuje całą tę pracę za Ciebie za kulisami.
JPQL umożliwia definiowanie zapytań za pomocą jednego z trzech następujących poleceń: SELECT, UPDATE lub DELETE. Warto zauważyć, że interfejs API EntityManager oferuje metody, które mogą być również używane do wykonywania operacji pobierania, aktualizacji i usuwania nad jednostkami. W szczególności są to metody find, merge i remove. Zastosowanie tych metod jest jednak zazwyczaj ograniczone do pojedynczej instancji encji, o ile oczywiście kaskadowanie nie wejdzie w życie. Natomiast instrukcje JPQL nie mają takiego ograniczenia—można definiować masowe operacje aktualizacji i usuwania nad zestawami encji oraz definiować zapytania zwracające zestawy instancji encji.
aby wysłać zapytanie JPQL z poziomu kodu Java, musisz użyć odpowiednich metod API EntityManager i API Query, wykonując następujące ogólne kroki:
- 1. Uzyskaj instancję EntityManager, używając injection lub bezpośrednio przez instancję EntityManagerFactory.
- 2. Utwórz instancję zapytania, wywołując odpowiednią metodę EntityManager, taką jak createQuery.
- 3. Ustaw parametr lub parametry zapytania, jeśli takie istnieją, za pomocą metody setParameter odpowiedniego zapytania.
- 4. W razie potrzeby ustaw maksymalną liczbę instancji do pobrania i/lub określ pozycję pierwszej instancji do pobrania, używając metod zapytania setmaxresult i / lub setFirstResult.
- 5. W razie potrzeby ustaw podpowiedź specyficzną dla dostawcy, używając metody zapytania setHint.
- 6. W razie potrzeby ustaw tryb flush dla wykonania zapytania za pomocą metody Setflushmode Query, nadpisując tryb flush menedżera encji.
- 7. Wykonaj zapytanie za pomocą odpowiedniej metody zapytania: getSingleResult lub getResultList. W przypadku operacji aktualizacji lub usuwania należy jednak użyć metody executeUpdate, która zwraca liczbę zaktualizowanych lub usuniętych wystąpień encji.
pełna lista metod interfejsu EntityManager, jak również metod interfejsu API zapytań, znajduje się w specyfikacji Enterprise JavaBeans 3.0: dokument Java Persistence API, który jest częścią JSR-220.
teraz, gdy masz ogólne pojęcie o tym, jak możesz utworzyć, a następnie wysłać zapytanie JPQL, możesz chcieć zobaczyć kilka praktycznych przykładów. Poniższy fragment kodu jest pobierany z metody doget serwletu, która używa zapytania JPQL do uzyskania informacji o wszystkich klientach przechowywanych w podstawowej tabeli relacyjnej powiązanej z jednostką klienta określoną w zapytaniu.
... @PersistenceUnit private EntityManagerFactory emf; public void doGet( ... EntityManager em = emf.createEntityManager(); PrintWriter out = response.getWriter(); List<Customer> arr_cust = (List<Customer>)em.createQuery("SELECT c FROM Customer c") .getResultList(); out.println("List of all customers: "+""); Iterator i = arr_cust.iterator(); Customer cust; while (i.hasNext()) { cust = (Customer) i.next(); out.println(cust.getCust_id()+""); out.println(cust.getCust_name()+""); out.println(cust.getEmail()+""); out.println(cust.getPhone()+""); out.println("----------------" + ""); } ...
szczególnie interesująca jest tutaj metoda createQuery instancji EntityManager oraz metoda getResultList instancji zapytania. Createquery Menedżera Entitymanagera jest używany do wytworzenia instancji zapytania, której metoda getResultList jest następnie używana do wykonania zapytania JPQL przekazanego do createQuery jako parametr. Jak można się domyślić, metoda getResultList zapytania zwraca wynik zapytania jako listę, której elementy, w tym konkretnym przykładzie, są rzucane do typu Customer.
jeśli chcesz pobrać pojedynczy wynik, interfejs API zapytań oferuje metodę getSingleResult, jak pokazano w poniższym przykładzie. Należy jednak pamiętać, że użycie getSingleResult spowoduje wyjątek, jeśli otrzymasz wiele wyników.
również ten przykład ilustruje użycie metody setParameter zapytania, dzięki której można powiązać argument z parametrem zapytania. Za pomocą setParameter można powiązać zarówno parametry nazwane, jak i pozycyjne. Tutaj jednak wiążesz nazwany parametr.
... Integer cust_id =2; Customer cust = (Customer)em.createQuery("SELECT c FROM Customer c WHERE c.cust_id=:cust_id") .setParameter("cust_id", cust_id) .getSingleResult(); out.println("Customer with id "+cust.getCust_id()+" is: "+ cust.getCust_name()+""); ...
warto zauważyć, że użycie instrukcji SELECT JPQL nie jest jedynym sposobem na pobranie pojedynczej instancji encji. Alternatywnie, możesz użyć metody znajdź EntityManager, która pozwala pobrać pojedynczą instancję encji na podstawie id encji przekazanego jako parametr.
w niektórych sytuacjach może być konieczne pobranie tylko niektórych informacji z docelowej instancji lub instancji encji, definiując zapytanie JPQL dla określonego pola lub pól encji. Tak wyglądałby powyższy fragment, jeśli chcesz pobrać tylko wartość pola cust_name instancji podmiotu Klienta, o którą zapytano tutaj:
... Integer cust_id =2; String cust_name = (String)em.createQuery("SELECT c.cust_name FROM Customer c WHERE c.cust_id=:cust_id") .setParameter("cust_id", cust_id) .getSingleResult(); out.println("Customer with id "+cust_id+" is: "+cust_name+""); ...
podobnie, aby uzyskać całą listę nazw klientów, możesz użyć następującego kodu:
... List<String> arr_cust_name = (List<String>)em.createQuery("SELECT c.cust_name FROM Customer c") .getResultList(); out.println("List of all customers: "+"<br/>"); Iterator i = arr_cust_name.iterator(); String cust_name; while (i.hasNext()) { cust_name = (String) i.next(); out.println(cust_name+"<br/>"); } ...
Wracając do SQL, możesz sobie przypomnieć, że lista select zapytania SQL może składać się z kilku pól z tabeli lub tabel określonych w klauzuli FROM. W JPQL można również użyć złożonej listy wyboru, wybierając dane tylko z interesujących pól encji. W takim przypadku należy jednak utworzyć klasę, do której zostanie oddany wynik zapytania. W poniższej sekcji zobaczysz przykład zapytania JPQL join, którego lista select składa się z pól pochodzących z więcej niż jednej jednostki.
Definiowanie JPQL łączy
podobnie jak SQL, JPQL pozwala definiować zapytania join. W języku SQL zwykle jednak definiuje się połączenie, które łączy rekordy z dwóch lub więcej tabel i / lub widoków, w tym tylko wymagane pola z tych tabel i widoków na liście select zapytania join. W przeciwieństwie do tego, lista select dla zapytania JPQL join zazwyczaj zawiera pojedynczą encję lub nawet jedno pole encji. Powodem tego jest natura technologii JPA. Po uzyskaniu wystąpienia encji można przejść do powiązanych z nim wystąpień przy użyciu odpowiednich metod getter. Takie podejście sprawia, że nie ma potrzeby definiowania zapytania, które zwróci wszystkie powiązane instancje jednostek jednocześnie.
na przykład, aby uzyskać informacje o zamówieniach wraz z ich pozycjami w SQL, musisz zdefiniować zapytanie join zarówno w tabelach purchaseOrders, jak i orderLineItems, określając pola z obu tabel na liście select zapytania. Podczas korzystania z JPQL możesz jednak zdefiniować zapytanie tylko nad entity PurchaseOrder,a następnie przejść do odpowiednich instancji OrderLineItem za pomocą metody getOrderLineItems PurchaseOrder. W tym przykładzie można zdefiniować zapytanie JPQL nad jednostkami PurchaseOrder i OrderLineItem tylko wtedy, gdy trzeba filtrować pobrane instancje PurchaseOrder na podstawie warunku lub warunków zastosowanych do OrderLineItem.
poniższy fragment pokazuje przykład zapytania JPQL join w akcji. Aby lepiej zrozumieć, w jaki sposób zaangażowane podmioty są ze sobą powiązane, możesz wrócić do rysunku 1 pokazanego w sekcji Przykładowa aplikacja wcześniej w artykule.
... Double max = (Double) em.createQuery("SELECT MAX(p.price) FROM PurchaseOrder o JOIN o.orderLineItems l JOIN l.product p JOIN p.supplier s WHERE s.sup_name = 'Tortuga Trading'") .getSingleResult(); out.println("The highest price for an ordered product supplied by Tortuga Trading: "+ max + "<br/>"); ...
w powyższym przykładzie korzystasz z funkcji MAX aggregate w klauzuli SELECT zapytania join w celu określenia produktu o najwyższej cenie, spośród tych, które zostały dostarczone przez Tortuga Trading i zostały zamówione co najmniej raz.
bardziej powszechną sytuacją jest jednak, gdy trzeba obliczyć, powiedzmy, całkowitą cenę zamówionych produktów, które zostały dostarczone przez określonego dostawcę. Tutaj może się przydać funkcja sumowania. W SQL takie zapytanie join może wyglądać tak:
SELECT SUM(p.price*l.quantity) FROM purchaseorders o JOIN orderlineitems l ON o.pono=l.pono JOIN products p ON l.prod_id=p.prod_id JOIN suppliers s ON p.sup_id=s.sup_id WHERE sup_name ='Tortuga Trading';
niestety, funkcja SUM używana w JPQL nie pozwala na przekazanie wyrażenia arytmetycznego jako argumentu. W praktyce oznacza to, że nie będziesz w stanie przekazać P. price*l.quantity jako argumentu do sumy JPQL. Istnieją jednak sposoby obejścia tego problemu. W poniższym przykładzie definiujesz klasę LineItemSum, której konstruktor zostanie następnie użyty na liście select zapytania, biorąc za parametry P. price I L.quantity. Konstruktor LineItemSum mnoży P. price przez L.quantity, zapisując wynik do zmiennej klasy Rslt. Następnie można iterację poprzez listę LineItemSum pobraną przez zapytanie, sumując wartości zmiennej Rslt LineItemSum. Poniższy fragment pokazuje, jak to wszystko można zaimplementować w kodzie:
package jpqlexample.servlets; ... class LineItemSum { private Double price; private Integer quantity; private Double rslt; public LineItemSum (Double price, Integer quantity){ this.rslt = quantity*price; } public Double getRslt () { return this.rslt; } public void setRslt (Double rslt) { this.rslt = rslt; } } public class JpqlJoinsServlet extends HttpServlet { ... public void doGet( ... List<LineItemSum> arr = (List<LineItemSum>)em.createQuery ("SELECT NEW jpqlexample.servlets.LineItemSum(p.price, l.quantity) FROM PurchaseOrder o JOIN o.orderLineItems l JOIN l.product p JOIN p.supplier s WHERE s.sup_name = 'Tortuga Trading'") .getResultList(); Iterator i = arr.iterator(); LineItemSum lineItemSum; Double sum = 0.0; while (i.hasNext()) { lineItemSum = (LineItemSum) i.next(); sum = sum + lineItemSum.getRslt(); } out.println("The total cost of the ordered products supplied by Tortuga Trading: "+ sum + "<br/>"); } }
powyższy przykład ilustruje między innymi, w jaki sposób można użyć niestandardowej klasy Java, a nie Klasy encji, na liście select zapytania JPQL, która zawiera pola pochodzące z więcej niż jednego encji, przenosząc wynik zapytania do tej klasy. W większości przypadków będziesz jednak musiał radzić sobie z zapytaniami, które otrzymają wystąpienie lub listę wystąpień określonego podmiotu.
pobrane instancje encji i bieżący kontekst trwałości
wyniki zapytania w przykładach artykułów zostały do tej pory po prostu wydrukowane. Jednak w rzeczywistych aplikacjach może być konieczne wykonanie dalszych operacji na wynikach zapytania. Na przykład może być konieczne zaktualizowanie pobranych wystąpień, a następnie utrzymanie ich z powrotem w bazie danych. Rodzi to pytanie: czy instancje pobierane przez zapytanie JPQL są gotowe do dalszego przetwarzania przez aplikację, czy wymagane są dodatkowe kroki, aby je przygotować? W szczególności interesujące byłoby dowiedzieć się, w jakim stanie, w odniesieniu do obecnego kontekstu trwałości, są pobrane instancje encji.
jeśli masz jakieś doświadczenie z Java Persistence, powinieneś wiedzieć, czym jest kontekst persistence. Podsumowując, kontekst trwałości jest zbiorem instancji encji zarządzanych przez instancję EntityManager powiązaną z tym kontekstem. W poprzednich przykładach użyłeś metody createQuery Entitymanagera do utworzenia instancji zapytania do wykonania zapytania JPQL. W rzeczywistości interfejs API EntityManager zawiera ponad dwadzieścia metod do zarządzania cyklem życia instancji encji, kontroli transakcji i tworzenia instancji zapytania, których metody są następnie używane do wykonania określonego zapytania i pobrania wyniku zapytania.
w odniesieniu do kontekstu trwałości instancja encji może znajdować się w jednym z następujących czterech stanów: Nowy, zarządzany, odłączony lub usunięty. Używając odpowiedniej metody EntityManager, możesz w razie potrzeby zmienić stan określonej instancji encji. Warto jednak zauważyć, że tylko instancje w stanie zarządzanym są synchronizowane z bazą danych, gdy nastąpi płukanie do bazy danych. Aby być dokładnym, instancje encji w usuniętym stanie są również synchronizowane, co oznacza, że rekordy bazy danych odpowiadające tym instancjom są usuwane z bazy danych.
natomiast wystąpienia w stanie nowym lub odłączonym nie będą synchronizowane z bazą danych. Na przykład, jeśli utworzysz nową instancję PurchaseOrder, a następnie wywołasz metodę flush EntityManager, inny rekord nie pojawi się w tabeli purchaseOrders, do której mapowana Jest jednostka PurchaseOrder. Dzieje się tak dlatego, że nowa instancja PurchaseOrder nie została dołączona do kontekstu trwałości. Oto jak może wyglądać kod:
... em.getTransaction().begin(); Customer cust = (Customer) em.find(Customer.class, 1); PurchaseOrder ord = new PurchaseOrder(); ord.setOrder_date(new Date()); ord.setCustomer(cust); em.getTransaction().commit(); ...
aby rozwiązać problem, musisz wywołać metodę persist EntityManager dla nowej instancji PurchaseOrder przed wywołaniem koloru, jak pokazano w poniższym przykładzie:
... em.getTransaction().begin(); Customer cust = (Customer) em.find(Customer.class, 1); PurchaseOrder ord = new PurchaseOrder(); ord.setOrder_date(new Date()); ord.setCustomer(cust); em.persist(ord); em.getTransaction().commit(); ...
alternatywnie, pod warunkiem, że podczas definiowania relacji z PurchaseOrder w jednostce klienta ustawiono opcję Cascade na PERSIST lub ALL, można dodać nowo utworzone wystąpienie PurchaseOrder do listy zleceń powiązanych z instancją klienta, zastępując operację persist następującą:
cust.getPurchaseOrders().add(ord);
powyższa dyskusja dotycząca Stanów instancji encji prowadzi nas do interesującego pytania, czy instancje encji pobrane przez zapytanie JPQL stają się automatycznie zarządzane, czy też trzeba zadbać o jawne ustawienie ich stanu na zarządzanie. Zgodnie ze specyfikacją JPA, niezależnie od sposobu pobierania jednostek—niezależnie od tego, czy jest to metoda find Entitymanagera, czy zapytanie—są one automatycznie dołączane do bieżącego kontekstu trwałości. Oznacza to, że instancje encji pobierane przez zapytanie JPQL są automatycznie zarządzane. Możesz na przykład zmienić wartość pola pobranej instancji, a następnie zsynchronizować tę zmianę z bazą danych, wywołując metodę flush Menedżera EntityManager lub zatwierdzając bieżącą transakcję. Nie musisz też martwić się o stan instancji powiązanych z pobranymi instancjami. Faktem jest, że przy pierwszym dostępie do powiązanej instancji staje się ona automatycznie zarządzana. Oto prosty przykład pokazujący, jak to wszystko działa w praktyce:
... em.getTransaction().begin(); PurchaseOrder ord = (PurchaseOrder)em.createQuery("SELECT o FROM PurchaseOrder o WHERE o.pono = 1") .getSingleResult(); List<OrderLineItem> items = ord.getOrderLineItems(); Integer qnt = items.get(0).getQuantity(); out.println("Quantity of the first item : "+ qnt +"<br/>"); items.get(0).setQuantity(qnt+1); qnt = items.get(0).getQuantity(); em.getTransaction().commit(); out.println("Quantity of the first item : "+ qnt +"<br/>"); ...
zauważ, że nie wywołujesz metody persist dla pobranej instancji PurchaseOrder, ani dla jej powiązanej instancji OrderLineItem, która jest tutaj modyfikowana. Pomimo tego, zmiany wprowadzone do pierwszej pozycji w zamówieniu zostaną zachowane w bazie danych po zatwierdzeniu transakcji. Dzieje się tak, ponieważ zarówno pobrane instancje encji, jak i ich powiązania są automatycznie dołączane do bieżącego kontekstu trwałości. Jak wspomniano wcześniej, te pierwsze stają się zarządzane po ich pobraniu, a te drugie są dołączane do kontekstu podczas uzyskiwania do nich dostępu.
w niektórych sytuacjach możesz chcieć, aby skojarzenia były dołączane do kontekstu Podczas wykonywania zapytania, a nie przy pierwszym dostępie. Tutaj przydaje się FETCH JOIN. Powiedzmy, że chcesz uzyskać wszystkie zamówienia należące do określonego klienta, po pobraniu tej instancji klienta. Takie podejście gwarantuje, że masz do czynienia z zamówieniami klientów dostępnymi w momencie wykonywania zapytania. Jeśli na przykład nowe zamówienie zostanie dodane do innego kontekstu, a następnie zsynchronizowane z bazą danych przed pierwszym uzyskaniem dostępu do listy zamówień powiązanych z pobraną instancją klienta, zmiana ta nie będzie widoczna, dopóki nie odświeżysz stanu instancji klienta z bazy danych. W poniższym fragmencie użyj zapytania join, które zwraca instancję klienta, której cust_id wynosi 1, i pobiera instancje PurchaseOrder powiązane z pobieraną instancją klienta.
... Customer cust = (Customer)em.createQuery("SELECT DISTINCT c FROM Customer c LEFT JOIN FETCH c.purchaseOrders WHERE c.cust_id=1") .getSingleResult(); ... List<PurchaseOrder> orders = cust.getPurchaseOrders(); ...
instancje jednostki PurchaseOrder, które nie są częścią jawnego wyniku zapytania, są również pobierane i dołączane do bieżącego kontekstu trwałości po wykonaniu zapytania.
wykorzystując natywne zapytania SQL
warto zauważyć, że nie ograniczasz się do JPQL podczas definiowania zapytań, które mają być następnie wykonywane za pomocą API zapytań. Możesz być zaskoczony, gdy dowiesz się, że API EntityManager oferuje metody tworzenia instancji zapytania do wykonywania natywnych instrukcji SQL. Najważniejszą rzeczą do zrozumienia o natywnych zapytaniach SQL utworzonych za pomocą metod EntityManager jest to, że, podobnie jak zapytania JPQL, zwracają instancje encji, a nie rekordy tabeli bazy danych. Oto prosty przykład dynamicznego natywnego zapytania SQL:
... List<Customer> customers = (List<Customer>)em.createNativeQuery ("SELECT * FROM customers", jpqlexample.entities.Customer.class) .getResultList(); Iterator i = customers.iterator(); Customer cust; out.println("Customers: " + "<br/>"); while (i.hasNext()) { cust = (Customer) i.next(); out.println(cust.getCust_name() +"<br/>"); } ...
JPQL wciąż się rozwija i nie ma wielu z tych ważnych funkcji dostępnych w SQL. W poprzedniej sekcji Definiowanie JPQL łączy, zobaczyłeś przykład niekompletności JPQL: musiałeś zrobić dużo pracy na własną rękę, ponieważ funkcja sumowania JPQL nie może przyjąć wyrażenia arytmetycznego jako parametru. Natomiast funkcja SUM SQL nie ma takiego ograniczenia. Jest to dobry przykład na to, gdzie zastąpienie JPQL natywnym SQL może być skuteczne. Poniższy kod ilustruje, w jaki sposób można uprościć rzeczy w tym konkretnym przykładzie, wybierając natywny SQL zamiast JPQL:
... String sup_name ="Tortuga Trading"; BigDecimal sum = (List)em.createNativeQuery("SELECT SUM(p.price*l.quantity) FROM orders o JOIN orderlineitems l ON o.pono=l.pono JOIN products p ON l.prod_id=p.prod_id JOIN suppliers s ON p.sup_id=s.sup_id WHERE sup_name =?1") .setParameter(1, sup_name) .getSingleResult(); out.println("The total cost of the ordered products supplied by Tortuga Trading: " + sum +"<br/>"); ...
powyższy przykład ilustruje między innymi, że można powiązać argumenty z natywnymi parametrami zapytań. W szczególności możesz powiązać argumenty z parametrami pozycyjnymi w taki sam sposób, jak w przypadku zapytania JPQL.
główną wadą natywnych zapytań jest złożoność wiązania wyników. W przykładzie zapytanie daje pojedynczy wynik typu prostego, unikając w ten sposób tego problemu. W praktyce jednak często mamy do czynienia z zestawem wyników typu złożonego. W takim przypadku będziesz musiał zadeklarować encję, na którą możesz zmapować swoje zapytanie natywne, lub zdefiniować złożony zestaw wyników zmapowany na wiele encji lub na mieszankę encji i wyników skalarnych.
Korzystanie z procedur składowanych
Inną Wadą zapytań natywnych jest to, że kod Java staje się bezpośrednio zależny od podstawowej struktury bazy danych. Jeśli zmodyfikujesz tę podstawową strukturę, będziesz musiał dostosować odpowiednie zapytania natywne w serwletach i / lub innych komponentach aplikacji, a następnie przekompilować i ponownie załadować te komponenty. Aby obejść ten problem, nadal korzystając z zapytań natywnych, możesz skorzystać z procedur składowanych, przenosząc złożone zapytania SQL do programów przechowywanych i wykonywanych w bazie danych, a następnie wywołując te zapisane programy zamiast wykonywać bezpośrednie połączenia do bazowych tabel. W praktyce oznacza to, że procedury składowane mogą oszczędzić Ci kłopotów związanych z obsługą bazowych tabel bezpośrednio z zapytań zakodowanych w kodzie Java. Zaletą takiego podejścia jest to, że w większości przypadków nie trzeba modyfikować kodu Java, aby śledzić zmiany w podstawowej strukturze bazy danych. Zamiast tego tylko procedury składowane będą wymagały naprawy.
Wracając do przykładu omówionego w poprzedniej sekcji, możesz przenieść złożone zapytanie join używane tam do funkcji przechowywanej, utworzonej w następujący sposób:
CREATE OR REPLACE FUNCTION sum_total(supplier VARCHAR2) RETURN NUMBER AS sup_sum NUMBER; BEGIN SELECT SUM(p.price*l.quantity) INTO sup_sum FROM orders o JOIN orderlineitems l ON o.pono=l.pono JOIN products p ON l.prod_id=p.prod_id JOIN suppliers s ON p.sup_id=s.sup_id WHERE sup_name = supplier; RETURN sup_sum; END; /
upraszcza to natywne zapytanie używane w kodzie Java i usuwa zależność z bazowych tabel:
... String sup_name ="Tortuga Trading"; BigDecimal sum = (BigDecimal)em.createNativeQuery("SELECT sum_total(?1) FROM DUAL") .setParameter(1, sup_name) .getSingleResult(); out.println("The total cost of the ordered products supplied by Tortuga Trading: " + sum +"<br/>"); ...
wniosek
jak nauczyłeś się w tym artykule, JPQL jest potężnym narzędziem, jeśli chodzi o dostęp do danych relacyjnych z poziomu aplikacji Java wykorzystujących Java Persistence. W JPQL, w przeciwieństwie do SQL/JDBC, definiujesz zapytania nad jednostkami JPA mapowanymi do bazowych tabel bazy danych, a nie odpytywać je bezpośrednio, w ten sposób zajmując się warstwą abstrakcji, która ukrywa szczegóły bazy danych z warstwy logiki biznesowej. Nauczyłeś się również, że JPQL nie jest jedyną opcją, jeśli chodzi o tworzenie zapytań nad encjami JPA—w niektórych sytuacjach korzystanie z natywnych zapytań SQL jest wygodniejsze.