Bez zbędnego gadania zapraszam do kolejnej części cyklu. Dzisiaj będą:
Managery
Są to beany warstwy EJB, poprawniej byłoby je nazwać Session Beanami (jakieś inne nazwy?).Nazwałem je managerami ze względu na pewną konwencje nazewniczą, która nakazuje nazywać je np OsobaManager.
Dostarczają metod do komunikacji z bazą danych (zapis, odczyt, modyfikacje).
Tak jak poprzednio proponuje zrobić osobną paczkę (np. pl.dzmitrow.beany ).
Manager to tak naprawdę dwa pliki. Pierwszym jest interfejs:
@LocalOd razu widać, że klasa, która będzie implementować ten interfejs będzie dostarczać możliwość dodawania, usuwania i szukania userów (encja Osoba).
public interface OsobaManagerLocal {
public void dodajUsera(Osoba osoba);
public void usunUsera(Osoba osoba);
public List<Osoba> szukajUserow(String login);
}
Adnotacja
@Local
mówi, że implementacja metod może być wykorzystywana tylko przez inne klasy tego EAR-a (jeżeli się mylę poprawcie mnie).Istnieje również
@Remote
dająca możliwość udostępniania metod. Wykonana przez nas implementacja będzie dostarczana przez serwer aplikacji pod odpowiednią nazwą JNDI.Nie będę się rozpisywał na temat
@Remote
, gdyż temat zasługuje na osobnego posta.Konwencja nakazuje zaznaczać w nazwie czy dany interfejs dotyczy zdalnego czy lokalnego użytku (stąd OsobaManagerLocal).
Przejdźmy do drugiego pliku, implementującego ten interfejs:
@Stateless
public class OsobaManager implements OsobaManagerLocal {
@PersistenceContext(unitName="ProjektEJB")
EntityManager em;
...
}
@Stateless
jak sama nazwa wskazuje wskazuje na bez stanowość beana (nie posiada on żadnych pól!).Są również
@Statefull
, ale nie mam z nimi żadnego doświadczenia więc je pominę.Zawiera jedynie definicje metod umożliwiających komunikacje z bazą. Poza warstwą EJB udostępnione są tylko te metody, które są podane w interfejsie.
@PeristenceContext
, z którego persistence unit (o ile mamy ich zdefiniowanych więcej) ma korzystać entity manager (nazwę zdefiniowaliśmy w persistence.xml).EntityManager em;
pobiera entity managera ze zdefiniowanego kontekstu.Niżej powinny oczywiście się znaleźć podane w interfejsie metody.
Zaimplementujmy dodawanie userów:
@OverrideWidzimy, że została wykorzystana metoda entity managera
public void dodajUsera(Osoba osoba) {
em.persist(osoba);
}
persist
. Powoduje ona zapisanie do bazy danych instancji encji (w tym wypadku encji Osoba).Pojawiła się też adnotacja
@Override
, większości z nas powinna być znana z standardowej javy. Oznacza ona metodę jako implementacje metody z interfejsu (lub nadpisanie metody z klasy, po której dziedziczymy).Dzięki temu możemy być pewni, że pozostanie spójność naszej implementacji z interfejsem.
Drobna uwaga. Przy tworzeniu obiektu osoba nie ustawiamy id! Zostanie on automatycznie dodany (o ile encja została tak skonfigurowana) przy zapisywaniu do bazy danych.
Zaimplementujmy teraz usuwanie:
@OverridePojawiło się kilka nowych metod entity managera.
public void usunUsera(Osoba osoba) {
Osoba o = em.merge(osoba);
em.remove(o);
em.flush();
}
merge
- bardzo ważna metoda gdyż odpowiada za kilka spraw. Po pierwsze odpowiada za wciąganie instancji encjido kontekstu entity managera. To znaczy, jeżeli taki obiekt istnieje w bazie danych to nasza instancja będzie z nim skojarzona.
Po drugie, jeśli obiekt był już w kontekście i zostały wprowadzone w nim zmiany to zostaną one zapisane do bazy danych. W przeciwnym wypadku zostanie on stworzony (jednak nie zaleca się z korzystania z
merge
zamiast persist
).remove(obiekt)
- usuwa obiekt z bazy danych. Obiekt musi być w kontekście!flush()
- niektóre operacje na bazie danych mogą być kolejkowane. Jeżeli chcemy być pewni by nasze polecenie zostało wykonane od razu wykonujemy flush() i wszystkie zmiany zostają zapisane.Pozostało nam zaimplementować szukanie:
@SuppressWarnings("unchecked")lub może trochę mniej bezpieczny, ale krótszy kod:
@Override
public List<Osoba> szukajUsera(String userName) {
Query query = em.createQuery("SELECT o FROM Osoba AS o where o.login like :login");
query.setParameter("login",userName+"%");
return query.getResultList();
}
@SuppressWarnings("unchecked")Znów coś nowego. Query jest to klasa pozwalająca na wykonywanie poleceń na bazie danych w języku PODOBNYM do SQL.
public List<Osoba> szukajUsera(String userName) {
return (em.createQuery("SELECT o FROM Osoba AS o where o.login like '"+userName+"%'")).getResultList();
}
Przykładem może byc Hibernate-owy HQL (Hibernate Query Language). Opis jak konstruować zapytania można znaleźć np. w dokumentacji hibernate-a.
W naszym przykładzie zapytanie tłumacząc na ludzki mówi:" z tabeli zawierającej ENCJE Osoba (
FROM Osoba
) pobierz takie instancje (SELECT o
), których login jest jak zawartość zmiennej login (like :login
)".Po czym za login podstawiamy userName+"%"
query.setParameter("login",userName+"%");
.Dzięki +"%" zwrócone będą osoby, których loginy zaczynają się od zawartości zmiennej
userName
. Korzystanie z setParameter() może być trochę mniej wygodne niż drugi przykład, ale jest bezpieczniejsze gdyż zmienne w ten sposób wprowadzane zmienne są czyszczone z potencjalnie niebezpiecznych wartości. Chroniąc nas w ten sposób przed atakami typu "SQL Injection".FROM Osoba AS o
pozwala na odnoszenie się w zapytaniu do pól osoby przez o.nazwaPola
.Należy zapamiętać, że
FROM
odnosi się do nazwy encji, a nie do nazwy tabeli.query.getResultList()
zwraca wynik zapytania jako listę. Jako, że metoda ta zwraca List
, a nie List<Osoba>
kompilator domaga się sprawdzenia czy zwracanie jest poprawne.Niestety nic sensownego nie wymyśliłem poza zmienną lokalną lub dodaniem
@SuppressWarnings("unchecked")
(co zostało zastosowane). Jeżeli ktoś ma lepszy pomysł proszę o komentarz.Oczywiście nie zawsze trzeba zwracać listę. Można zawsze zwrócić pojedynczy wynik przez
getSingleResult();
(zwracany jest obiekt klasy Object
więc konieczne są rzutowania).Istnieją jeszcze "Named Query", ale co to i jak tego używać pozostawiam na pastwę ciekawości czytelnika.
Na deser sprawa poruszona w poprzednim poście czyli inicjalizacja pól z fetch ustawionym na
FetchType.LAZY
.W hibernacie wystarczy wykonać:
Hibernate.initialize(d.getProdukty());
W ten sposób zostanie zainicjalizowane pole produkty i będziemy mogli pobrać jego zawartość (jeżeli ktoś wie jak zrobić to w OpenJPA proszę pisać w komentarzach).
To by było chyba wszystko.
Jak zwykle zapraszam do komentowania.
Jak już jesteśmy przy wypisywaniu obiektów z tabeli, to jak wypisać konkretne jej pole (konkretne pole klasy encji)? Natknąłem się na przykład wykorzystujący metodę find() oraz @TransactionAttribute(REQUIRED), ale to nie działa. Macie jakiś pomysł?
OdpowiedzUsuńQuery query = em.createQuery("SELECT o.imie FROM Osoba AS o where o.login='jakisLogin'");
OdpowiedzUsuńString imie = (String)query.getSingleResult();
Lub po prostu pobrac obiekt cały i wybraną przez nas property.
Jezeli mamy poprawnie zdefiniowane encje wszystko powinno działac.