Marcin Kapusta

Marcin Kapusta iOS
Developer/Software
Developer/Music
Producer

Temat: Zrozumieć sesję w Hibernate [Początkujący]

Witajcie...

Mam głównie pytanie teoretyczne i fajnie by było jakby ktoś
umiał odpowiedzieć gdyż nie wiem czy korzystamy z hibernate we
właściwy sposób.

Mamy taką klasę i wykorzystujemy jedną sesję. Kiedy już raz
utworzymy sesję to ją wykorzystujemy, niezależnie od tego jaki
wątek z niej korzysta. Oto klasa.


public class DatabaseDao {

private static SessionFactory sessionFactory;
private static Session session;

public DatabaseDao() {
try {
sessionFactory = new
Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
System.err.println("Initial SessionFactory creation failed." +
ex);
throw new ExceptionInInitializerError(ex);
}
}

public Session getSession(){
//getSessionFactory().getc
if(session == null || !session.isOpen()){
session = getSessionFactory().openSession();
}
return session;
}

private SessionFactory getSessionFactory() {
return sessionFactory;
}

public boolean testConnection(){
try{
return !getSession().connection().isClosed();
}catch(Exception ex){
return false;
}
}

public void dispose(){
if(session != null && session.isOpen()){
session.close();
}
if(sessionFactory != null && !sessionFactory.isClosed()){
sessionFactory.close();
}
}

}



Pytanie jest takie? Czy dobrze robimy? Czy przypadkiem nie powinno
być tak, że sesję tworzymy nową dla każdego requesta, a
następnie ją zamykamy.

Druga sprawa jest taka, że jak wykorzystujemy powyższą klasę w
servlecie, aby dostać się do sesji i wykorzystujemy tą sesję
tak:


Session session = getSession();
Stacja s = (Stacja) session.load(Stacja.class, stacjaId);


To stacja cały czas zwracana jest taka sama. Powiedzmy, że
ładuję stronę, odpala się servlet i pobierane są dane o stacji
z bazy danych. Następnie inną aplikacją modyfikuje wiersz dla tej
stacji i zapisuje zmieny. W bazie mam już zmieniony wiersz. W
przeglądarce daje F5, odświerzam stronę, a tu proszę - cały
czas widzę stare dane tak jakby w ogóle nie było zapytania do
bazy danych.

Jeśli ktoś mógłby mi pomóc będę bardzo wdzięczny. Staram
się zrozumieć ten niby super framework, ale ciężko to przychodzi
i ciągle napotykam na jakieś schody. Z góry dziękuje.
Piotr Skoczek

Piotr Skoczek SonarMind, Java
Developer

Temat: Zrozumieć sesję w Hibernate [Początkujący]

Session to obiekt lekki, który reprezentuje jednostkę pracy z bazą danych, nie jest bezpieczny wątkowo, a zatem powinieneś go tworzyć dla każdego wywołania request ( w danym momencie tylko dla jednego wątku) . Z kolei SessionFactory jest bezpieczny wątkowo, ale dosyć kosztowny w tworzeniu, czyli: SessionFactory tylko raz, Session za każdym razem gdy coś potrzebujesz. Ważne jest zrozumienie, w jakich stanach może być obiekt i jak współpracuje z Hibernatem, (poczytaj o trzech stanach i jak je obsłużyć: nowy, utrwalony, odłączony). Sesja działa też jako bufor, więc być może dlatego nie widać zmiany. Spróbuj session.flush() w celu opróżnienia sesji. Żeby zrozumieć Hibernate, nie ucz się tylko pisania, ale poczytaj jak naprawdę to działa, wtedy rozwiąże Ci się wiele problemów( automatyczne rozpoznawanie modelu obiektowego, opóźniona inicjalizacja etc. )

Przykład:

Session session = HibernateUtil.getSessionFactory().openSession();
session.getTransaction().begin();
session.save(administrator);
session.getTransaction().commit();


Administrator tmp = (Administrator) session.get(Administrator.class,id);
System.out.println(""+tmp.getEditable());
session.close();Piotr Skoczek edytował(a) ten post dnia 18.05.11 o godzinie 16:33
Tomasz Radwański

Tomasz Radwański Java \ JEE developer

Temat: Zrozumieć sesję w Hibernate [Początkujący]

Piotr Skoczek:
Session to obiekt lekki, który reprezentuje jednostkę pracy z bazą danych, nie jest bezpieczny wątkowo, a zatem powinieneś go tworzyć dla każdego wywołania request ( w danym momencie tylko dla jednego wątku) . Z kolei SessionFactory jest bezpieczny wątkowo, ale dosyć kosztowny w tworzeniu, czyli: SessionFactory tylko raz, Session za każdym razem gdy coś potrzebujesz. Ważne jest zrozumienie, w jakich stanach może być obiekt i jak współpracuje z Hibernatem, (poczytaj o trzech stanach i jak je obsłużyć: nowy, utrwalony, odłączony). Sesja działa też jako bufor, więc być może dlatego nie widać zmiany. Spróbuj session.flush() w celu opróżnienia sesji. Żeby zrozumieć Hibernate, nie ucz się tylko pisania, ale poczytaj jak naprawdę to działa, wtedy rozwiąże Ci się wiele problemów( automatyczne rozpoznawanie modelu obiektowego, opóźniona inicjalizacja etc. )

Generalnie wszystko się zgadza, z tym, że w pewnych warunkach sesja może być wirtualnie rozciągnięta. Spring WebFlow, Seam i podobne frameworki pozwalają utrzymać (lub symulować utrzymanie) sesję między requestami. Moim zdaniem przysparza to więcej problemów niż korzyści. Jeżeli jednak chcesz trzymać otwartą sesję między requestami, pogoogluj za OpenSessionInView (jest nawet gotowy filtr Hibernate).

W Twoim przypadku lepiej za każdym razem tworzyć sesję za pomocą SessionFactory.

Teraz sprawa nieodświeżania encji - przy kolejnych odczytach encja jest czytana z cache sesji, nie z bazy. Jeżeli przy odświeżeniu strony utworzysz nową sesję, problem zniknie. W przeciwnym razie wystarczy session.refresh(..).
Warto poczytać też o OptimisticLocking.
Marcin Kapusta

Marcin Kapusta iOS
Developer/Software
Developer/Music
Producer

Temat: Zrozumieć sesję w Hibernate [Początkujący]

Bardzo Wam dziękuje... Tak też myślałem, że mamy coś skopane.

Czyli na koniec requesta, gdy już skończymy pobierać dane z bazy...

session.flush();
session.close();

Ok. przypuśmy, że zamknę sesję. Co w przypadku gdy do widoku w jsp. przekaże obiekt, którego wywołanie obiekt.getOgloszenia() powoduje ładowanie ogłoszeń z bazy tzw... Lazy loading. Przypuszczam, że wywali się błąd gdyż sesja została w servlecie zamknięta.

Macie jakiś sposób na to...
Piotr Skoczek

Piotr Skoczek SonarMind, Java
Developer

Temat: Zrozumieć sesję w Hibernate [Początkujący]

Musisz zrozumieć, iż sesja z Hibernate nie jest taka sama jak HttpSession z JEE. Pozwala Ci na wydobycie obiektu, wtedy kiedy potrzebujesz. Jeśli dalej używasz ten obiekt (poza sesją ) jest w stanie odłączonym(zawsze możesz zapisać jego stan poprzez nową sesje używając update(obiektOdłączony)). Natomiast lazy loading możesz wyłączyć w pliku .hbm.xml przypisując dla danego mapowania lazy='false', ale obawiam się, iż dla dużego zbioru danych możesz stracić na wydajności.

Z dokumentacji:
"
Hibernate does not support lazy initialization for detached objects.

Alternatively, you can use a non-lazy collection or association, by specifying lazy="false" for the association mapping. However, it is intended that lazy initialization be used for almost all collections and associations. If you define too many non-lazy associations in your object model, Hibernate will fetch the entire database into memory in every transaction.

On the other hand, you can use join fetching, which is non-lazy by nature, instead of select fetching in a particular transaction."

Ewentualnie, jesli odwołasz się do właściwości w zasięgu sesji, to zostanie ona wczytana z bazy danych dla tego obiektu, a potem możesz ten obiekt przekazać z wypełnioną już właściwością (domyslam się, że to np. kolekcja). Piotr Skoczek edytował(a) ten post dnia 18.05.11 o godzinie 19:43
Tomasz Radwański

Tomasz Radwański Java \ JEE developer

Temat: Zrozumieć sesję w Hibernate [Początkujący]

Jeżeli chcesz zamknąć sesję, musisz pobrać wszystkie dane przed zamknięciem. Lazy loading kolekcji (one-to-many i many-to-many) często lepiej zastąpić odpowiednim zapytaniem (select a from A a join fetch a.b...), nie zmieniałbym mapowania na eager. Alternatywnie (niezbyt ładnie) można dociągnąć kolekcję przed zamknięciem sesji (np. odwołując się do 1 elementu).

Po zamknięciu sesji encja przechodzi w stan detached. W celu wykonania operacji bazodanowych (np. lazy loading) możesz podłączyć ją z powrotem do innej sesji (session.merge lub session.saveOrUpdate).

Używasz może Springa? Spring (EJB i inne frameworki też) pozwala na deklaratywne zarządzanie sesją, transakcjami itp. np. przez anotacje. Prosty przykład masz tu:

http://www.shoesobjects.com/blog/2004/11/21/1101083542...
Piotr T.

Piotr T. Spring/Microservices

Temat: Zrozumieć sesję w Hibernate [Początkujący]

Marcin Kapusta:

Ok. przypuśmy, że zamknę sesję. Co w przypadku gdy do widoku w jsp. przekaże obiekt, którego wywołanie obiekt.getOgloszenia() powoduje ładowanie ogłoszeń z bazy tzw... Lazy loading. Przypuszczam, że wywali się błąd gdyż sesja została w servlecie zamknięta.

Macie jakiś sposób na to...

umożliwić obiektowi dociągnięcie ogłoszeń z bazy
jeśli pole ogloszenia jest null.
szkic poniżej


public class Obiekt {
//pola nie mapowane w hbm
private OgloszeniaDAO ogloszeniaDAO;
...
//mapowane pola w hbm.xml

public List getOgloszenia(){
if (null ==ogloszenia){
ogloszenia = ogloszeniaDAO.findByObiekt(this);
}
return ogloszenia;
}

}
//interfejs do pobierania obiektów
public interface ObiektDAO{
public Obiekt findById(int id);
}

public class ObiektHiberanateImpl implements ObiektDAO{
public Obiekt findById(int id){
...
}
}

public class ObiektDependencyServiceInjectDAO implements ObiektDAO{
private ObiektDAO internalDAO;

private OgloszenieDAO ogloszenieDAO;

public obiekt findById(int id){
Obiekt obiekt = internalDAO.findById(id);//pobiera goły obiekt
if (null == obiekt){
return null;
}
obiekt.setOgloszeniaDAO(ogloszenieDAO);
return obiekt;
}
}


Piotr T. edytował(a) ten post dnia 18.05.11 o godzinie 20:31
Jakub Grabowski

Jakub Grabowski Tworzenie aplikacji,
integracja SOA,
outsourcing,
szkolen...

Temat: Zrozumieć sesję w Hibernate [Początkujący]

Piotr T.:
Marcin Kapusta:
umożliwić obiektowi dociągnięcie ogłoszeń z bazy
jeśli pole ogloszenia jest null.
szkic poniżej


public class Obiekt {
//pola nie mapowane w hbm
private OgloszeniaDAO ogloszeniaDAO;
...
//mapowane pola w hbm.xml

public List getOgloszenia(){
if (null ==ogloszenia){
ogloszenia = ogloszeniaDAO.findByObiekt(this);
}
return ogloszenia;
}
Moim wstawianie pola z DAO do klasy, którą to DAO ma zarządzać to brzydki trick.
Zdecydowanie lepsze jest podejście wspomniane przez Tomasza. Jestem przeciwnikiem robienia dwukierunkowych relacji, jeśli nie zawsze potrzebujemy nawigacji w obie strony. Jeśli w większości przypadków będziesz potrzebował ogłoszeń dla obiektu to wybierz EAGER dla mapowania. Jeśli "dociągasz" dane na żądanie, a ogłoszeń może być dużo to lepiej napisz sobie metodę w DAO, która pobierze ogłoszenia dla danego obiektu.
Piotr T.

Piotr T. Spring/Microservices

Temat: Zrozumieć sesję w Hibernate [Początkujący]

Jakub Grabowski:
Moim wstawianie pola z DAO do klasy, którą to DAO ma zarządzać to brzydki trick.
zwykłe dependency injection
... Jeśli w większości przypadków będziesz potrzebował ogłoszeń dla obiektu to wybierz EAGER dla mapowania.
zasadniczo tak, lecz to nie na temat ;)
...Jeśli "dociągasz" dane na żądanie, a ogłoszeń może być dużo to lepiej napisz sobie metodę w DAO, która pobierze ogłoszenia dla danego obiektu.
a co wywoła tą metodę w DAO ?
chciałbym zobaczyć szkic rozwiązania , może będzie lepsze ?
Piotr Wierzbowski

Piotr Wierzbowski IT Architect, Asseco
Poland S.A.

Temat: Zrozumieć sesję w Hibernate [Początkujący]

Piotr T.:
Jakub Grabowski:
Moim wstawianie pola z DAO do klasy, którą to DAO ma zarządzać to brzydki trick.
zwykłe dependency injection

Zgodzę się z Jakubem, tak sie raczej nie robi. Obiekt, który trzyma dane, nie zarządza ich ładowaniem, zasada Separation of Concerns. Tak samo nie mamy przecież EntityManagera w klasach Entity
... Jeśli w większości przypadków będziesz potrzebował ogłoszeń dla obiektu to wybierz EAGER dla mapowania.
zasadniczo tak, lecz to nie na temat ;)

Zasadniczo domyślnie jest LAZY, tak tez to przewidzieli twórcy JPA (jak nie podasz, że jest EAGER to jest LAZY). Ja EAGER ustawiam tylko gdy nie ma opcji zeby wybierac podzbiór kolekcji. Do wybierania czesci kolekcji używam query.
...Jeśli "dociągasz" dane na żądanie, a ogłoszeń może być dużo to lepiej napisz sobie metodę w DAO, która pobierze ogłoszenia dla danego obiektu.
a co wywoła tą metodę w DAO ?
chciałbym zobaczyć szkic rozwiązania , może będzie lepsze ?

Wywołac powinna to metoda która tworzy obiekt idący na stronę.

np. w Spring MVC

'Objekt' to ta klasa z ogłoszeniami i jakimiś innymi polami, encja JPA

public TaKlasaController {
...
@Autowired
private MyDAO myDAO;
...
public ModelAndView taMetoda() {
ModelAndView mv = new ModelAndView();
Objekt o = myDAO.getObjektWithOgłoszenia(jakiesIdObiektu);
mv.addObject("objekt", o);
return mv;
}
...
}

public MyDAOImpl implements MyDAO {

...
public Objekt getObjektWithOgłoszenia(int id) {
...
Query q = entityManager.createQuery("from Cat as cat
left join cat.kittens as kitten
with kitten.bodyWeight > 10.0
where cat.id = :id").setParam("id", id);
return q.getsingleResult();
}
...

}
Piotr T.

Piotr T. Spring/Microservices

Temat: Zrozumieć sesję w Hibernate [Początkujący]

Piotr Wierzbowski:
'Objekt' to ta klasa z ogłoszeniami i jakimiś innymi polami, encja JPA

public TaKlasaController {
...
}
}
Ok jeśli 'Obiekt' to tylko pudełko na dane to jest to ok.



Wyślij zaproszenie do