Wielu z Was – korzystających z narzędzi ORM często zastanawiała się zapewne jak przekazać do zapytania bazodanowego listę, wg której miałyby być przeszukiwane krotki. Czy można w ogóle przekazać listę parametrów za klauzulę WHERE? Odpowiedź brzmi – tak.
Standardowo, wykorzystanie javax.persistence.Query wiąże się z zapytaniem ściśle powiązanym z encjami bazodanowymi w projekcie. Każde odwołanie dotyczy zbioru nazw w ramach encji – najprościej ujmując. Tutaj manipulowanie lista parametrów w ramach klauzuli WHERE po wielu godzinach poszukiwań rozwiązania uznaję za niemożliwe, a i wujek Google nie okazał się zbyt pomocny.
Ale skoro nie można wejść przez drzwi, to dlaczego nie wejść przez otwarte okno? Ano właśnie. Tutaj przydaje się połączenie ORM Hibernate z JPA. Okazuje się, że warto, a oprócz w/w zalet są również inne. O tym za chwilę.
„Framework do realizacji warstwy dostępu do danych (ang. persistence layer). Zapewnia on przede wszystkim translację danych pomiędzy relacyjną bazą danych a światem obiektowym (ang. O/R mapping). Opiera się na wykorzystaniu opisu struktury danych za pomocą języka XML, dzięki czemu można ‚rzutować’ obiekty, stosowane w obiektowych językach programowania, takich jak Javabezpośrednio na istniejące tabele bazy danych. Dodatkowo Hibernate zwiększa wydajność operacji na bazie danych dzięki buforowaniu i minimalizacji liczby przesyłanych zapytań.” [1]
O co nam chodzi?
Wyobraźmy sobie, że mamy zbiór firm. Firmy te posiadają jakieś specjalizacje. Tabelka ManyToMany odzwierciedla to powiązanie. Specjalizacja ma unikalny identyfikator.
Teraz mamy pobrać tylko te firmy, których identyfikator specjalizacji znajduje się na liście identyfikatorów przesłanych przez nas. W takim wypadku Query (JPA) zamieniamy na SQLQuery (Hibernate). Wiąże się to z operowanie na przestrzeni nazw bazy danych. Encje nas nie obchodzą – przynajmniej bezpośrednio. Popatrzcie na kod:
EntityManager em = jpaTemplate.getEntityManagerFactory().createEntityManager(); Object delegate = em.getDelegate(); if(delegate instanceof Session) { Session session = (Session) delegate; List<CompanyDTO> companies; String q = "select distinct c.id as companyid, c.name as name, c.email as email from companies c where " + "((select count(*) from companies_categories oc where oc.companies_id=c.id and oc.types_of_projects_id in(:types_of_project)) > 0)"; SQLQuery query = session.createSQLQuery(q); query.setProperties(params); query.addScalar("companyid"); query.addScalar("name"); query.addScalar("email"); query.setFirstResult(start); query.setMaxResults(count); query.setResultTransformer(Transformers.aliasToBean(CompanyDTO.class)); companies = ((List<CompanyDTO>) query.list()); return companies; }
Zacznijmy od tego, że zwracane obiekty to CompanyDTO, co wiąże się z pobieraniem wyłącznie tego, co nas interesuje. Transformacja odbywa się po aliasach (konieczne wyłącznie małe litery, by wszystko działało – tryb camelCase powoduje problemy na etapie mapowania). Klasa CompanyDTO to sztuczna encja – prywatne pola o nazwach identycznych jak aliasy i gettery/settery dla tych pól.
Parametry do zapytania przekazywane są przez funkcję setProperties(HashMap<String, Object> map). W naszym przykładzie w tej mapie znajduje się parametr „types_of_project” z listą (ArrayList<Integer>) identyfikatorów typów kategorii, który jest zbindowany w zapytaniu w klauzuli IN. Aby można było skorzystać z DTO należy do zapytania dodać skalary odzwierciedlające wszystkie aliasy.
Przyjemności w użytkowaniu. Pozdrawiam!