Jak naprawić org.hibernate.LazyInitializationException - nie można zainicjować proxy - bez sesji

188

Otrzymuję następujący wyjątek:

Exception in thread "main" org.hibernate.LazyInitializationException: could not initialize proxy - no Session
    at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:167)
    at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:215)
    at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:190)
    at sei.persistence.wf.entities.Element_$$_jvstc68_47.getNote(Element_$$_jvstc68_47.java)
    at JSON_to_XML.createBpmnRepresantation(JSON_to_XML.java:139)
    at JSON_to_XML.main(JSON_to_XML.java:84)

kiedy próbuję zadzwonić z głównego, następujące linie:

Model subProcessModel = getModelByModelGroup(1112);
System.out.println(subProcessModel.getElement().getNote());

Zaimplementowałem tę getModelByModelGroup(int modelgroupid)metodę po pierwsze:

public static Model getModelByModelGroup(int modelGroupId, boolean openTransaction) {

    Session session = SessionFactoryHelper.getSessionFactory().getCurrentSession();     
    Transaction tx = null;

    if (openTransaction) {
        tx = session.getTransaction();
    }

    String responseMessage = "";

    try {
        if (openTransaction) {
            tx.begin();
        }
        Query query = session.createQuery("from Model where modelGroup.id = :modelGroupId");
        query.setParameter("modelGroupId", modelGroupId);

        List<Model> modelList = (List<Model>)query.list(); 
        Model model = null;

        for (Model m : modelList) {
            if (m.getModelType().getId() == 3) {
                model = m;
                break;
            }
        }

        if (model == null) {
            Object[] arrModels = modelList.toArray();
            if (arrModels.length == 0) {
                throw new Exception("Non esiste ");
            }

            model = (Model)arrModels[0];
        }

        if (openTransaction) {
            tx.commit();
        }

        return model;

   } catch(Exception ex) {
       if (openTransaction) {
           tx.rollback();
       }
       ex.printStackTrace();
       if (responseMessage.compareTo("") == 0) {
           responseMessage = "Error" + ex.getMessage();
       }
       return null;
    }
}

i dostałem wyjątek. Następnie przyjaciel zaproponował mi, żebym zawsze testował sesję i pobierał bieżącą sesję, aby uniknąć tego błędu. Więc zrobiłem to:

public static Model getModelByModelGroup(int modelGroupId) {
    Session session = null;
    boolean openSession = session == null;
    Transaction tx = null;
    if (openSession) {
        session = SessionFactoryHelper.getSessionFactory().getCurrentSession(); 
        tx = session.getTransaction();
    }
    String responseMessage = "";

    try {
        if (openSession) {
            tx.begin();
        }
        Query query = session.createQuery("from Model where modelGroup.id = :modelGroupId");
        query.setParameter("modelGroupId", modelGroupId);

        List<Model> modelList = (List<Model>)query.list(); 
        Model model = null;

        for (Model m : modelList) {
            if (m.getModelType().getId() == 3) {
                model = m;
                break;
            }
        }

        if (model == null) {
            Object[] arrModels = modelList.toArray();
            if (arrModels.length == 0) {
                throw new RuntimeException("Non esiste");
            }

            model = (Model)arrModels[0];

            if (openSession) {
                tx.commit();
            }
            return model;
        } catch(RuntimeException ex) {
            if (openSession) {
                tx.rollback();
            }
            ex.printStackTrace();
            if (responseMessage.compareTo("") == 0) {
                responseMessage = "Error" + ex.getMessage();
            }
            return null;        
        }
    }
}

ale nadal otrzymujesz ten sam błąd. Dużo czytałem o tym błędzie i znalazłem kilka możliwych rozwiązań. Jednym z nich było ustawienie lazyLoad na false, ale nie wolno mi tego robić, dlatego zaproponowano mi kontrolowanie sesji

Blerta Dhimitri
źródło

Odpowiedzi:

93

To, co jest nie tak, to fakt, że konfiguracja zarządzania sesją jest ustawiona na zamykanie sesji po zatwierdzeniu transakcji. Sprawdź, czy masz coś takiego:

<property name="current_session_context_class">thread</property>

w twojej konfiguracji.

Aby rozwiązać ten problem, możesz zmienić konfigurację fabryki sesji lub otworzyć inną sesję i tylko poprosić o te leniwie załadowane obiekty. Ale proponuję tutaj zainicjować tę leniwą kolekcję w samym getModelByModelGroup i wywołać:

Hibernate.initialize(subProcessModel.getElement());

kiedy nadal jesteś w aktywnej sesji.

I ostatnia rzecz. Przyjazna rada. W swojej metodzie masz coś takiego:

for (Model m : modelList) {
    if (m.getModelType().getId() == 3) {
        model = m;
        break;
    }
}

Proszę wstawić ten kod, po prostu filtruj modele o typie id równym 3 w instrukcji zapytania zaledwie kilka wierszy powyżej.

Trochę więcej czytania:

konfiguracja fabryki sesji

problem z sesją zamkniętą

goroncy
źródło
1
Dziękuję Ci! Rozwiązałem swój problem za pomocą openSession () zamiast getCurrentSession (), ponieważ jeden z linków, które mi podałeś, sugeruje to, ale teraz obawiam się, że to źle
Blerta Dhimitri
2
Nie, prawdopodobnie jest w porządku. Ale czytaj więcej, aby móc w pełni kontrolować swoje sesje i transakcje. Bardzo ważne jest, aby znać podstawy, ponieważ wszystkie technologie wyższego poziomu, takie jak Spring, Hibernacja i inne, działają na tej samej koncepcji.
goroncy
179

Jeśli użyjesz Spring oznacz klasę jako @Transactional , to Spring zajmie się zarządzaniem sesjami.

@Transactional
public class MyClass {
    ...
}

Za pomocą @Transactionalwielu ważnych aspektów, takich jak propagacja transakcji, obsługiwane są automatycznie. W takim przypadku, jeśli zostanie wywołana inna metoda transakcyjna, będzie ona miała możliwość dołączenia do bieżącej transakcji, unikając wyjątku „bez sesji”.

OSTRZEŻENIE Jeśli używasz @Transactional, pamiętaj o wynikającym z tego zachowaniu. W tym artykule znajdziesz typowe pułapki. Na przykład aktualizacje jednostek są utrzymywane, nawet jeśli nie wywołujesz jawnie połączeniasave

użytkownik2601995
źródło
21
Nie mogę przecenić znaczenia tej odpowiedzi. Naprawdę polecam najpierw wypróbowanie tej opcji.
sparkyspider
6
Pamiętaj również, że musisz dodać @EnableTransactionManagementdo konfiguracji, aby umożliwić transakcje. „ jeśli nazywa się inną metodę transakcyjną, metoda będzie miała opcję dołączenia do bieżącej transakcji ”, to zachowanie jest różne dla różnych sposobów realizacji transakcji, tj. proxy interfejsu vs proxy klasy lub tkanie AspectJ. Zapoznaj się z dokumentacją .
Erik Hofer,
1
Czy powinniśmy zrozumieć, że Transactionaladnotacja z wiosny jest zatem zalecana nie tylko do modyfikowania transakcji, ale także do uzyskiwania dostępu tylko do nich?
Stephane
8
Poważnie polecam użycie tej adnotacji na szczycie klasy tylko do testowania. Prawdziwy kod powinien oznaczać każdą metodę osobno jako transakcję w klasie. Chyba że wszystkie metody w klasie będą wymagały otwartego połączenia z transakcją z bazą danych.
m1ld
1
Czy nie jest bezpiecznie wstawiać @Transactional (readOnly = true) zamiast tylko @Transactional?
Hamedz
105

Możesz spróbować ustawić

<property name="hibernate.enable_lazy_load_no_trans">true</property>

w hibernacji.cfg.xml lub persistence.xml

Problem, o którym należy pamiętać z tą właściwością, jest dobrze wyjaśniony tutaj

Wilianto Indrawan
źródło
8
Czy możesz również wyjaśnić jego znaczenie?
Mohit Kanwar,
2
Jestem też ciekawy, co to robi. Naprawiłem problem, który miałem, ale chciałbym zrozumieć, dlaczego.
Hassan
3
dla persistence.xml:<property name="hibernate.enable_lazy_load_no_trans" value="true"/>
ACV
33
Zdajesz sobie sprawę, że hibernate.enable_lazy_load_no_transto anty-wzory , prawda?
Vlad Mihalcea,
6
NIE UŻYWAJ TEGO NIERUCHOMOŚCI, JEŚLI WIOSNA ZARZĄDZA TWOIMMI TRANSAKCJAMI NINIEJSZA NIERUCHOMOŚĆ PROWADZI DO WYBUCHU TRANSAKCJI, PROSTO WIOSNA
WYŁĄCZA
54

Najlepszym sposobem na poradzenie sobie z tymLazyInitializationException jest skorzystanie z JOIN FETCHdyrektywy:

Query query = session.createQuery(
    "from Model m " +
    "join fetch m.modelType " +
    "where modelGroup.id = :modelGroupId"
);

W każdym razie NIE używaj następujących Anti-Patternów, jak sugerują niektóre odpowiedzi:

Czasami projekcja DTO jest lepszym wyborem niż pobieranie encji, w ten sposób nie dostaniesz żadnej LazyInitializationException.

Vlad Mihalcea
źródło
Jak rozpoznać, które połączenie ma problem? Trudno mi rozpoznać połączenie. Czy jest jakiś sposób ? Użyłem do celów testowych FetchType=EAGER, ale to nie jest poprawne rozwiązanie, prawda?
Shantaram Tupe
Wystarczy użyć rejestrowania. A EAGER jest zły, tak.
Vlad Mihalcea
Następnie powinieneś użyć DTO lub zainicjować wszystkie powiązania przed opuszczeniem @Transactionalusługi.
Vlad Mihalcea
2
Powinniśmy opowiadać się za najlepszą praktyką w przedsiębiorstwie, ale nie za szybką poprawką.
etlds
21

Otrzymałem ten sam błąd dla relacji jeden do wielu dla poniższych adnotacji.

@OneToMany(mappedBy="department", cascade = CascadeType.ALL)

Zmieniono jak poniżej po dodaniu fetch = FetchType.EAGER, działało to dla mnie.

@OneToMany(mappedBy="department", cascade = CascadeType.ALL, fetch=FetchType.EAGER)
Smruti R Tripathy
źródło
26
Tak, może to naprawić, ale teraz ładujesz całe drzewo danych. W większości przypadków będzie to miało negatywny wpływ na wydajność
astro8891
13

jeśli używasz danych wiosennych jpa, rozruchu wiosennego, możesz dodać ten wiersz w pliku application.properties

spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
Shaaban Ebrahim
źródło
7
Jest to uważane za anty-wzór. vladmihalcea.com/…
Sudip Bhandari
9

Ten wyjątek ze względu na to, że kiedy zadzwonisz session.getEntityById(), sesja zostanie zamknięta. Musisz więc ponownie dołączyć encję do sesji. Lub Łatwe rozwiązanie to po prostu skonfiguruj default-lazy="false" do swojego entity.hbm.xmllub jeśli używasz adnotacji, po prostu dodaj @Proxy(lazy=false)do swojej klasy encji.

Reddeiah Pidugu
źródło
5

Napotkałem ten sam problem. Myślę, że innym sposobem na to jest to, że możesz zmienić zapytanie, aby dołączyć, aby pobrać Element z Modelu w następujący sposób:

Query query = session.createQuery("from Model m join fetch m.element where modelGroup.id = :modelGroupId")
Tony Vu
źródło
4

Oznacza to, że obiekt, do którego próbujesz uzyskać dostęp, nie jest ładowany, więc napisz zapytanie, które pobierze sprzężenie z obiektem, do którego próbujesz uzyskać dostęp.

Na przykład:

Jeśli próbujesz uzyskać ObjectB z ObjectA, gdzie ObjectB jest kluczem obcym w ObjectA.

Zapytanie:

SELECT objA FROM ObjectA obj JOIN FETCH obj.objectB objB
Rex Roy
źródło
3

Jest tu kilka dobrych odpowiedzi, które radzą sobie z tym błędem w szerokim zakresie. W Spring Security natrafiłem na konkretną sytuację, która szybko i pewnie nie była optymalna.

Podczas autoryzacji użytkownika (natychmiast po zalogowaniu i przejściu uwierzytelnienia) testowałem encję użytkownika pod kątem określonych uprawnień w klasie niestandardowej, która rozszerza SimpleUrlAuthenticationSuccessHandler.

Moja jednostka użytkownika implementuje UserDetails i ma zestaw Leniwie załadowanych ról, które zgłosiły wyjątek „org.hibernate.LazyInitializationException - nie można zainicjować proxy - brak sesji”. Zmiana tego zestawu z „fetch = FetchType.LAZY” na „fetch = FetchType.EAGER” naprawiła to dla mnie.

Nocna sowa
źródło
2

Napotkano ten sam wyjątek w innym przypadku użycia.

wprowadź opis zdjęcia tutaj

Przypadek użycia: Spróbuj odczytać dane z DB z projekcją DTO.

Rozwiązanie: Użyj metody get zamiast ładowania .

Ogólne działanie

public class HibernateTemplate {
public static Object loadObject(Class<?> cls, Serializable s) {
    Object o = null;
    Transaction tx = null;
    try {
        Session session = HibernateUtil.getSessionFactory().openSession();
        tx = session.beginTransaction();
        o = session.load(cls, s); /*change load to get*/
        tx.commit();
        session.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return o;
}

}

Klasa trwałości

public class Customer {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "Id")
private int customerId;

@Column(name = "Name")
private String customerName;

@Column(name = "City")
private String city;

//constructors , setters and getters

}

Interfejs CustomerDAO

public interface CustomerDAO 
     {
   public CustomerTO getCustomerById(int cid);
     }

Klasa obiektu transferu jednostki

public class CustomerTO {

private int customerId;

private String customerName;

private String city;

//constructors , setters and getters

}

Klasa fabryczna

public class DAOFactory {

static CustomerDAO customerDAO;
static {
    customerDAO = new HibernateCustomerDAO();
}

public static CustomerDAO getCustomerDAO() {
    return customerDAO;
}

}

DAO specyficzne dla jednostki

public class HibernateCustomerDAO implements CustomerDAO {

@Override
public CustomerTO getCustomerById(int cid) {
    Customer cust = (Customer) HibernateTemplate.loadObject(Customer.class, cid);
    CustomerTO cto = new CustomerTO(cust.getCustomerId(), cust.getCustomerName(), cust.getCity());
    return cto;
}

}

Pobieranie danych: klasa testowa

CustomerDAO cdao = DAOFactory.getCustomerDAO();
CustomerTO c1 = cdao.getCustomerById(2);
System.out.println("CustomerName -> " + c1.getCustomerName() + " ,CustomerCity -> " + c1.getCity());

Obecne dane

wprowadź opis zdjęcia tutaj

Zapytania i dane wyjściowe generowane przez system hibernacji

Hibernacja: wybierz klienta0_.Id jako Id1_0_0_, customer0_.City jako City2_0_0_, customer0_.Nazwa jak Name3_0_0_ z CustomerLab31 customer0_ gdzie where0_0_.Id =?

CustomerName -> Cody, CustomerCity -> LA

Km
źródło
1

Jeśli używasz Grail'sframeworka, łatwo jest rozwiązać leniwy wyjątek inicjujący , używając Lazysłowa kluczowego w określonym polu w klasie domeny.

Na przykład:

class Book {
    static belongsTo = [author: Author]
    static mapping = {
        author lazy: false
    }
}

Znajdź więcej informacji tutaj

Zeb
źródło
1

W moim przypadku session.clear()przyczyną tego problemu było niewłaściwe umieszczenie .

absin
źródło
1

Oznacza to, że używasz JPA lub hibernacji w swoim kodzie i wykonujesz operację modyfikacji na DB bez dokonywania transakcji logiki biznesowej. Tak prostym rozwiązaniem jest zaznaczenie swojego kodu @Transactional

Aman Goel
źródło
-1

używa session.get (*. class, id); ale nie ładuj funkcji

Artavazd Manukyan
źródło
3
czy możesz to wyjaśnić?
nowy dzień
-2

możesz również rozwiązać ten problem, dodając lazy = false do pliku * .hbm.xml lub możesz zainicjować swój obiekt w Hibernate.init (Object), gdy otrzymasz obiekt z db

Sandeep Roniyaar
źródło
10
generalnie dodanie lazy = false nie jest dobrym pomysłem. dlatego leniwy jest domyślnie prawdziwy
MoienGK
OP wyraźnie wcześniej powiedział, że nie wolno mu tego robić.
GingerHead
-2

Wykonaj następujące zmiany w pliku servlet-context.xml

    <beans:property name="hibernateProperties">
        <beans:props>

            <beans:prop key="hibernate.enable_lazy_load_no_trans">true</beans:prop>

        </beans:props>
    </beans:property>
ArunDhwaj IIITH
źródło