Comment réparer org.hibernate.LazyInitializationException - impossible d'initialiser le proxy - pas de session

200

J'obtiens l'exception suivante:

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)

lorsque j'essaye d'appeler depuis les principales lignes suivantes:

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

J'ai d'abord implémenté la getModelByModelGroup(int modelgroupid)méthode comme ceci:

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;
    }
}

et a obtenu l'exception. Puis un ami m'a suggéré de toujours tester la session et d'obtenir la session en cours pour éviter cette erreur. Alors j'ai fait ceci:

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;        
        }
    }
}

mais toujours, obtenez la même erreur. J'ai beaucoup lu cette erreur et j'ai trouvé des solutions possibles. L'un d'eux était de définir lazyLoad sur false mais je ne suis pas autorisé à le faire, c'est pourquoi on m'a suggéré de contrôler la session

Blerta Dhimitri
la source

Réponses:

95

Ce qui ne va pas ici, c'est que votre configuration de gestion de session est définie pour fermer la session lorsque vous validez la transaction. Vérifiez si vous avez quelque chose comme:

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

dans votre configuration.

Afin de surmonter ce problème, vous pouvez modifier la configuration de la fabrique de session ou ouvrir une autre session et demander uniquement ces objets chargés différés. Mais ce que je suggérerais ici est d'initialiser cette collection paresseuse dans getModelByModelGroup lui-même et d'appeler:

Hibernate.initialize(subProcessModel.getElement());

lorsque vous êtes toujours en session active.

Et une dernière chose. Un conseil amical. Vous avez quelque chose comme ça dans votre méthode:

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

Veuillez installer ce code, filtrez simplement les modèles avec un identifiant de type égal à 3 dans l'instruction de requête juste quelques lignes ci-dessus.

Quelques lectures supplémentaires:

configuration d'usine de session

problème avec session fermée

Goroncy
la source
1
Merci! J'ai résolu mon problème en utilisant openSession () au lieu de getCurrentSession () comme l'un des liens que vous m'avez suggéré, mais maintenant j'ai peur si c'est mal de le faire
Blerta Dhimitri
2
Non, c'est probablement bien. Mais lisez un peu plus pour être en mesure de contrôler pleinement vos sessions et transactions. Il est vraiment important de connaître les bases, car toutes les technologies de niveau supérieur comme Spring, Hibernate et plus fonctionnent sur le même concept.
goroncy
191

Si vous utilisez Spring marquez la classe comme @Transactional , Spring gérera la gestion de session.

@Transactional
public class MyClass {
    ...
}

En utilisant @Transactional, de nombreux aspects importants tels que la propagation des transactions sont traités automatiquement. Dans ce cas, si une autre méthode transactionnelle est appelée, la méthode aura la possibilité de rejoindre la transaction en cours en évitant l'exception «pas de session».

AVERTISSEMENT Si vous l'utilisez @Transactional, soyez conscient du comportement qui en résulte. Consultez cet article pour les pièges courants. Par exemple, les mises à jour des entités sont persistantes même si vous n'appelez pas explicitementsave

user2601995
la source
22
Je ne saurais trop insister sur l’importance de cette réponse. Je recommanderais sérieusement d'essayer cette option en premier.
sparkyspider
6
Notez également que vous devez ajouter @EnableTransactionManagementà votre configuration pour activer les transactions. " si une autre méthode transactionnelle est appelée, la méthode aura la possibilité de rejoindre la transaction en cours " ce comportement est différent pour les différentes façons dont les transactions sont implémentées, c'est-à-dire le proxy d'interface vs le proxy de classe ou le tissage AspectJ. Reportez-vous à la documentation .
Erik Hofer
1
Doit-on comprendre que l' Transactionalannotation Spring est donc conseillée, non seulement pour modifier les transactions, mais aussi pour accéder uniquement à celles-ci?
Stephane
8
Je recommanderais sérieusement d'utiliser cette annotation en haut de la classe uniquement pour les tests. Le code réel doit marquer chaque méthode comme transaction dans la classe séparément. À moins que toutes les méthodes de la classe ne nécessitent une connexion ouverte avec transaction à la base de données.
m1ld
1
N'est-il pas prudent de mettre @Transactional (readOnly = true) au lieu de simplement @Transactional?
Hamedz
104

Vous pouvez essayer de définir

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

dans hibernate.cfg.xml ou persistence.xml

Le problème à garder à l'esprit avec cette propriété est bien expliqué ici

Wilianto Indrawan
la source
8
Pouvez-vous également expliquer sa signification?
Mohit Kanwar
2
Je suis également curieux de savoir ce que cela fait. Cela a résolu le problème que j'avais, mais j'aimerais comprendre pourquoi.
Hassan
Après des milliers de "mettez simplement lazy = false dans votre fichier de configuration" et "initialisez simplement chaque objet avec Hibernate.initialize ()", enfin une solution concrète et viable pour moi. Cette option devrait être rendue standard en hibernate!
saimiris_devel
3
pour persistence.xml:<property name="hibernate.enable_lazy_load_no_trans" value="true"/>
ACV
7
N'UTILISEZ PAS CETTE PROPRIÉTÉ, SI LE PRINTEMPS GÈRE VOS TRANSACTIONS, CETTE PROPRIÉTÉ ENTRAÎNERA UNE EXPLOSION DE TRANSACTIONS, SIMPLEMENT LE PRINTEMPS
ARRÊTERA
64

La meilleure façon de gérer leLazyInitializationException est d'utiliser la JOIN FETCHdirective:

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

Quoi qu'il en soit, n'utilisez PAS les Anti-Patterns suivants comme suggéré par certaines des réponses:

Parfois, une projection DTO est un meilleur choix que la récupération d'entités, et de cette façon, vous n'en aurez pas LazyInitializationException.

Vlad Mihalcea
la source
Comment puis-je identifier quel appel rencontre un problème? J'ai du mal à identifier l'appel. Y a-t-il un moyen? À des fins de test, j'ai utilisé FetchType=EAGER, mais ce n'est pas la bonne solution, non?
Shantaram Tupe
Utilisez simplement la journalisation. Et EAGER est mauvais, oui.
Vlad Mihalcea
Ensuite, vous devez utiliser les DTO ou initialiser toutes les associations avant de quitter le @Transactionalservice.
Vlad Mihalcea
2
Nous devrions prôner la meilleure pratique d'entreprise, mais pas la solution miracle.
etlds
1
C'est la meilleure solution
phamductri
22

J'obtenais la même erreur pour une relation un à plusieurs pour l'annotation ci-dessous.

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

Modifié comme ci-dessous après avoir ajouté fetch = FetchType.EAGER, cela a fonctionné pour moi.

@OneToMany(mappedBy="department", cascade = CascadeType.ALL, fetch=FetchType.EAGER)
Tripathie Smruti R
la source
30
Oui, cela peut résoudre le problème, mais vous chargez maintenant tout l'arborescence de données. Cela aura des impacts négatifs sur les performances dans la plupart des cas
astro8891
15

si vous utilisez spring data jpa, spring boot, vous pouvez ajouter cette ligne dans application.properties

spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
Shaaban Ebrahim
la source
9
Ceci est considéré comme un anti-pattern. vladmihalcea.com/…
Sudip Bhandari
9

Cette exception en raison du moment où vous appelez session.getEntityById(), la session sera fermée. Vous devez donc rattacher l'entité à la session. Ou la solution facile est simplement configurée default-lazy="false" à votre entity.hbm.xmlou si vous utilisez des annotations, ajoutez simplement @Proxy(lazy=false)à votre classe d'entité.

R Pidugu
la source
5

J'ai rencontré le même problème. Je pense qu'une autre façon de résoudre ce problème est que vous pouvez modifier la requête pour rejoindre récupérer votre élément à partir du modèle comme suit:

Query query = session.createQuery("from Model m join fetch m.element where modelGroup.id = :modelGroupId")
Tony Vu
la source
5

Cela signifie que l'objet auquel vous essayez d'accéder n'est pas chargé, alors écrivez une requête qui effectue une extraction par jointure de l'objet auquel vous essayez d'accéder.

Par exemple:

Si vous essayez d'obtenir ObjectB à partir d'ObjectA où ObjectB est une clé étrangère dans ObjectA.

Requete :

SELECT objA FROM ObjectA obj JOIN FETCH obj.objectB objB
rex roy
la source
3

Il y a plusieurs bonnes réponses ici qui gèrent cette erreur dans une large portée. J'ai rencontré une situation spécifique avec Spring Security qui avait une solution rapide, mais probablement pas optimale.

Pendant l'autorisation de l'utilisateur (immédiatement après la connexion et le passage de l'authentification), je testais une entité utilisateur pour une autorité spécifique dans une classe personnalisée qui étend SimpleUrlAuthenticationSuccessHandler.

Mon entité utilisateur implémente UserDetails et a un ensemble de rôles chargés paresseusement qui a jeté l'exception "org.hibernate.LazyInitializationException - n'a pas pu initialiser le proxy - pas de session". Changer cet ensemble de "fetch = FetchType.LAZY" à "fetch = FetchType.EAGER" a résolu ce problème pour moi.

Hibou de nuit
la source
2

Face à la même exception dans différents cas d'utilisation.

entrez la description de l'image ici

Cas d'utilisation: essayez de lire les données de la base de données avec la projection DTO.

Solution: utilisez la méthode get au lieu de load .

Opération générique

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;
}

}

Classe de persistance

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

}

Interface CustomerDAO

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

Classe d'objets de transfert d'entité

public class CustomerTO {

private int customerId;

private String customerName;

private String city;

//constructors , setters and getters

}

Classe d'usine

public class DAOFactory {

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

public static CustomerDAO getCustomerDAO() {
    return customerDAO;
}

}

DAO spécifique à l'entité

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;
}

}

Récupération des données: classe de test

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

Présenter les données

entrez la description de l'image ici

Requête et sortie générées par Hibernate System

Hibernate: sélectionnez customer0_.Id comme Id1_0_0_, customer0_.City comme City2_0_0_, customer0_.Name comme Name3_0_0_ de CustomerLab31 customer0_ where customer0_.Id =?

CustomerName -> Cody, CustomerCity -> LA

Kms
la source
1

Si vous utilisez Grail'sFramework, il est simple de résoudre l' exception d'initialisation différée en utilisant un Lazymot-clé sur un champ spécifique dans la classe de domaine.

Par exemple:

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

Trouvez plus d'informations ici

Zeb
la source
1

Dans mon cas, un égaré session.clear()causait ce problème.

absine
la source
1

Cela signifie que vous utilisez JPA ou hibernate dans votre code et effectuez une opération de modification sur DB sans effectuer la transaction de logique métier. La solution si simple pour cela est de marquer votre morceau de code @Transactional

Aman Goel
la source
-1

utilise session.get (*. class, id); mais ne charge pas la fonction

Artavazd Manukyan
la source
3
Pouvez-vous expliquer cela?
jour du
-2

vous pouvez également le résoudre en ajoutant lazy = false dans votre fichier * .hbm.xml ou vous pouvez initier votre objet dans Hibernate.init (Object) lorsque vous obtenez un objet de db

Sandeep Roniyaar
la source
10
généralement ajouter lazy = false n'est pas une bonne idée. c'est pourquoi la paresse est vrai par défaut
MoienGK
Le PO a clairement dit à l'avance qu'il n'était pas autorisé à le faire.
GingerHead
-2

Effectuez les modifications suivantes dans 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
la source