web-dev-qa-db-fra.com

Comment résoudre le problème «Exception Hibernate non réussie pour initialiser paresseusement une collection de rôles»

J'ai ce problème:

org.hibernate.LazyInitializationException: échec d'initialisation paresseuse d'une collection de rôles: mvc3.model.Topic.comments, aucune session ou session n'a été fermée

Voici le modèle:

@Entity
@Table(name = "T_TOPIC")
public class Topic {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int id;

    @ManyToOne
    @JoinColumn(name="USER_ID")
    private User author;

    @Enumerated(EnumType.STRING)    
    private Tag topicTag;

    private String name;
    private String text;

    @OneToMany(mappedBy = "topic", cascade = CascadeType.ALL)
    private Collection<Comment> comments = new LinkedHashSet<Comment>();

    ...

    public Collection<Comment> getComments() {
           return comments;
    }

}

Le contrôleur, qui appelle model, se présente comme suit:

@Controller
@RequestMapping(value = "/topic")
public class TopicController {

    @Autowired
    private TopicService service;

    private static final Logger logger = LoggerFactory.getLogger(TopicController.class);


    @RequestMapping(value = "/details/{topicId}", method = RequestMethod.GET)
    public ModelAndView details(@PathVariable(value="topicId") int id)
    {

            Topic topicById = service.findTopicByID(id);
            Collection<Comment> commentList = topicById.getComments();

            Hashtable modelData = new Hashtable();
            modelData.put("topic", topicById);
            modelData.put("commentList", commentList);

            return new ModelAndView("/topic/details", modelData);

     }

}

La page jsp ressemble à ce qui suit:

<%@page import="com.epam.mvc3.helpers.Utils"%>
<%@ page language="Java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://Java.Sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
      <title>View Topic</title>
</head>
<body>

<ul>
<c:forEach items="${commentList}" var="item">
<jsp:useBean id="item" type="mvc3.model.Comment"/>
<li>${item.getText()}</li>

</c:forEach>
</ul>
</body>
</html>

L'exception est levée lors de l'affichage de jsp. Dans la ligne avec c: forEach loop

296
Eugene

Si vous savez que vous voudrez voir tous les Comments chaque fois que vous récupérerez un Topic, changez le mappage de vos champs pour comments en:

@OneToMany(fetch = FetchType.EAGER, mappedBy = "topic", cascade = CascadeType.ALL)
private Collection<Comment> comments = new LinkedHashSet<Comment>();

Les collections sont chargées par défaut, regardez this si vous voulez en savoir plus.

175
darrengorman

D'après mon expérience, j'ai les méthodes suivantes pour résoudre la fameuse exception LazyInitializationException:

(1) Utilisez Hibernate.initialize

Hibernate.initialize(topics.getComments());

(2) Utilisez JOIN FETCH

Vous pouvez utiliser la syntaxe JOIN FETCH dans votre JPQL pour extraire explicitement la collection enfant. C'est un peu comme EAGER chercher.

(3) Use OpenSessionInViewFilter

LazyInitializationException se produit souvent dans la couche de vue. Si vous utilisez la structure Spring, vous pouvez utiliser OpenSessionInViewFilter. Cependant, je ne vous suggère pas de le faire. Cela peut entraîner des problèmes de performances s’il n’est pas utilisé correctement.

157
Boris

L'origine de votre problème:

Par défaut, hibernate charge paresseusement les collections (relations), ce qui signifie que chaque fois que vous utilisez le collectiondans votre code (ici commentsdans la classe Topicname__), le problème est que vous récupérez la collection dans votre contrôleur (où la session JPA est fermée). Il s’agit de la ligne de code qui provoque l’exception (où vous chargez la collection commentsname__):

    Collection<Comment> commentList = topicById.getComments();

Vous obtenez une collection de "commentaires" (topic.getComments ()) dans votre contrôleur (où JPA session a pris fin), ce qui provoque une exception. De plus, si vous aviez la collection commentsdans votre fichier jsp comme ceci (au lieu de l'obtenir dans votre contrôleur):

<c:forEach items="topic.comments" var="item">
//some code
</c:forEach>

Vous auriez toujours la même exception pour la même raison.

Résoudre le problème:

Parce que vous ne pouvez avoir que deux collections avec le FetchType.Eager (collection extraite avec empressement) dans une classe Entity et que le chargement différé est plus efficace que le chargement avec impatience, je pense que cette façon de résoudre votre problème est préférable au simple changement de FetchTypename__:

Si vous souhaitez que la collection soit initialisée paresseuse et que cela fonctionne également, il est préférable d’ajouter cet extrait de code à votre web.xml:

<filter>
    <filter-name>SpringOpenEntityManagerInViewFilter</filter-name>
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>SpringOpenEntityManagerInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Ce code fait qu’il augmentera la longueur de votre JPA session ou, comme le dit la documentation, il est utilisé "to allow for lazy loading in web views despite the original transactions already being completed.". Ainsi, la session JPA sera ouverte un peu plus longtemps et vous permettra de charger des collections dans vos fichiers jsp. classes de contrôleur.

47
gandalf

Je sais que c'est une vieille question mais je veux aider. Vous pouvez mettre l’annotation transactionnelle sur la méthode de service dont vous avez besoin. Dans ce cas, findTopicByID (id) devrait avoir

@Transactional(propagation=Propagation.REQUIRED, readOnly=true, noRollbackFor=Exception.class)

plus d'informations sur cette annotation peuvent être trouvées ici

A propos des autres solutions:

fetch = FetchType.EAGER 

n’est pas une bonne pratique, il convient de l’utiliser UNIQUEMENT si nécessaire.

Hibernate.initialize(topics.getComments());

L'initialiseur d'hibernation lie vos classes à la technologie d'hibernation. Si vous souhaitez être flexible, ce n'est pas une bonne façon de faire.

J'espère que ça aide

42
sarbuLopex

La raison en est que lorsque vous utilisez la charge différée, la session est fermée.

Il y a deux solutions.

  1. Ne pas utiliser la charge paresseuse.

    Définissez lazy=false en XML ou Définissez @OneToMany(fetch = FetchType.EAGER) dans une annotation.

  2. Utilisez la charge paresseuse.

    Définissez lazy=true en XML ou Définissez @OneToMany(fetch = FetchType.LAZY) dans une annotation.

    et ajoutez OpenSessionInViewFilter filter dans votre web.xml

Détail Voir mon POST .

29
saneryee

Le problème est dû à l'accès à un attribut avec la session de veille prolongée fermée. Vous n'avez pas de transaction d'hibernation dans le contrôleur.

Solutions possibles:

  1. Faites toute cette logique, dans la couche service, (avec le @Transactional), pas dans le contrôleur. Il devrait y avoir le bon endroit pour faire cela, cela fait partie de la logique de l'application, pas du contrôleur (dans ce cas, une interface pour charger le modèle). Toutes les opérations de la couche service doivent être transactionnelles. c'est-à-dire: déplacez cette ligne vers la méthode TopicService.findTopicByID:

    Collection commentList = topicById.getComments ();

  2. tilisez 'impatient' au lieu de 'paresseux'. Maintenant, vous n’utilisez pas "paresseux". Ce n’est pas une solution réelle. Si vous voulez utiliser du paresseux, fonctionne comme une solution de contournement temporaire (très temporaire).

  3. tilisez @Transactional dans le contrôleur. Il ne devrait pas être utilisé ici, vous mélangez la couche de service avec la présentation, ce n'est pas une bonne conception.
  4. tilisez OpenSessionInViewFilter, nombreux inconvénients signalés, instabilité possible.

En général, la meilleure solution est le 1.

18
abentan

Pour charger une collection paresseuse, une session doit être active. Dans une application Web, il existe deux manières de procéder. Vous pouvez utiliser le modèle Ouvrir une session dans la vue , où vous utilisez un intercepteur pour ouvrir la session au début de la demande et la fermer à la fin. Le risque est que vous ayez une gestion solide des exceptions ou que vous puissiez bloquer toutes vos sessions et que votre application puisse se bloquer.

Une autre façon de gérer cela consiste à collecter toutes les données dont vous avez besoin dans votre contrôleur, à fermer votre session, puis à les insérer dans votre modèle. Personnellement, je préfère cette approche car elle semble un peu plus proche de l’esprit du modèle MVC. De même, si vous obtenez une erreur de la base de données de cette manière, vous pourrez la gérer beaucoup mieux que si cela se produisait dans votre moteur de rendu de vue. Votre ami dans ce scénario est Hibernate.initialize (myTopic.getComments ()). Vous devrez également rattacher l'objet à la session, car vous créez une nouvelle transaction avec chaque demande. Utilisez session.lock (myTopic, LockMode.NONE) pour cela.

18
GMK
@Controller
@RequestMapping(value = "/topic")
@Transactional

je résous ce problème en ajoutant @Transactional, je pense que cela peut rendre la session ouverte

17
RuiZhi Wang

Si vous essayez d’avoir une relation entre une entité et une collection ou une liste d’objets Java (par exemple, type long), vous voudrez quelque chose comme ceci:

@ElementCollection(fetch = FetchType.EAGER)
    public List<Long> ids;
11
javaboygo

J'ai découvert que déclarer @PersistenceContext en tant que EXTENDED résout également ce problème:

@PersistenceContext(type = PersistenceContextType.EXTENDED)
9
Elcin ABD

Comme je l'ai expliqué dans cet article , le meilleur moyen de gérer le LazyInitializationException est de le récupérer au moment de l'interrogation, comme suit:

select t
from Topic t
left join fetch t.comments

Vous devriez TOUJOURS éviter les anti-modèles suivants:

Par conséquent, assurez-vous que vos associations FetchType.LAZY sont initialisées au moment de la requête ou dans la portée @Transactional d'origine à l'aide de Hibernate.initialize pour les collections secondaires.

6
Vlad Mihalcea

L'annotation @Transactional sur le contrôleur est manquante

@Controller
@RequestMapping("/")
@Transactional
public class UserController {
}
6
Xcoder

votre liste est paresseuse, donc la liste n'a pas été chargée. Appeler pour figurer sur la liste ne suffit pas. utiliser dans Hibernate.initialize afin d’initialiser la liste. Si dosnt fonctionne sur l'élément de la liste et appelle Hibernate.initialize pour chacun. cette nécessité doit être avant de revenir de la portée de la transaction. regardez this post.
rechercher -

Node n = // .. get the node
Hibernate.initialize(n); // initializes 'parent' similar to getParent.
Hibernate.initialize(n.getChildren()); // pass the lazy collection into the session 
5
Avihai Marchiano

c’est le problème que j’ai rencontré récemment et que j’ai résolu avec

<f:attribute name="collectionType" value="Java.util.ArrayList" />

description plus détaillée ici et cela m'a sauvé la journée.

5
tolgayilmaz

Pour résoudre le problème dans mon cas, il manquait juste cette ligne

<tx:annotation-driven transaction-manager="myTxManager" />

dans le fichier de contexte d'application.

L'annotation @Transactional sur une méthode n'a pas été prise en compte.

J'espère que la réponse aidera quelqu'un

4
Mario Biasi

Une des meilleures solutions consiste à ajouter les éléments suivants dans votre fichier application.properties: spring.jpa.properties.hibernate.enable_lazy_load_no_trans = true

4
sreekmatta

En utilisant l'annotation hibernate @Transactional, si vous obtenez un objet de la base de données avec des attributs extraits paresseux, vous pouvez simplement les obtenir en récupérant ces attributs comme suit:

@Transactional
public void checkTicketSalePresence(UUID ticketUuid, UUID saleUuid) {
        Optional<Ticket> savedTicketOpt = ticketRepository.findById(ticketUuid);
        savedTicketOpt.ifPresent(ticket -> {
            Optional<Sale> saleOpt = ticket.getSales().stream().filter(sale -> sale.getUuid() == saleUuid).findFirst();
            assertThat(saleOpt).isPresent();
        });
}

Ici, dans une transaction gérée par proxy Hibernate, le fait d'appeler ticket.getSales() permet d'effectuer une autre requête pour extraire les ventes, car vous l'avez explicitement demandé.

3
Alex

Dans mon cas, le code suivant posait problème:

entityManager.detach(topicById);
topicById.getComments() // exception thrown

Parce qu’elle s’est détachée de la base de données et que Hibernate n’a plus extrait la liste du terrain au moment voulu. Alors je l'initialise avant de détacher:

Hibernate.initialize(topicById.getComments());
entityManager.detach(topicById);
topicById.getComments() // works like a charm
2
kiedysktos

Pour ceux qui travaillent avec Critères , j'ai constaté que

criteria.setFetchMode("lazily_fetched_member", FetchMode.EAGER);

a fait tout ce que j'avais besoin avait fait.

Le mode de récupération initiale des collections est défini sur FetchMode.LAZY pour offrir des performances optimales, mais lorsque j'ai besoin des données, j'ajoute simplement cette ligne pour profiter des objets entièrement remplis.

2
velis

La collection commentsdans votre classe de modèle Topicest chargée paresseusement, ce qui est le comportement par défaut si vous ne l'annotez pas spécifiquement avec fetch = FetchType.EAGER.

Il est fort probable que votre service findTopicByIDutilise une session Hibernate sans état. Une session sans état ne possède pas le cache de premier niveau, c'est-à-dire pas de contexte de persistance. Plus tard, lorsque vous essayez d’itérer commentsname__, Hibernate lève une exception.

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: mvc3.model.Topic.comments, no session or session was closed

La solution peut être:

  1. Annoter commentsavec fetch = FetchType.EAGER

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "topic", cascade = CascadeType.ALL)   
    private Collection<Comment> comments = new LinkedHashSet<Comment>();
    
  2. Si vous souhaitez toujours que les commentaires soient chargés paresseusement, utilisez les sessions avec état de Hibernate , de manière à pouvoir récupérer les commentaires plus tard, à la demande.

1
Yuci

Bonjour à tous, en postant assez tard, espérons que cela aidera les autres, Remerciant d’avance @GMK pour ce message Hibernate.initialize (object)

quand Lazy = "true"

Set<myObject> set=null;
hibernateSession.open
set=hibernateSession.getMyObjects();
hibernateSession.close();

maintenant, si j'accède à 'set' après la fermeture de la session, une exception est levée.

Ma solution:

Set<myObject> set=new HashSet<myObject>();
hibernateSession.open
set.addAll(hibernateSession.getMyObjects());
hibernateSession.close();

maintenant, je peux accéder à 'set' même après la fermeture de Hibernate Session.

0
vic

Encore une autre façon de faire la chose, vous pouvez utiliser TransactionTemplate pour envelopper la récupération paresseuse. Comme

Collection<Comment> commentList = this.transactionTemplate.execute
(status -> topicById.getComments());
0
aristotll

Dans mon cae, j’avais la cartographie en noir et blanc A et B comme

Un a

@OneToMany(mappedBy = "a", cascade = CascadeType.ALL)
Set<B> bs;

dans la couche DAO, la méthode doit être annotée avec @Transactional si vous n'avez pas annoté le mappage avec Fetch Type - Eager

0
Shirgill Farhan

La raison en est que vous essayez d’obtenir la liste de commentaires sur votre contrôleur après la fermeture de la session dans le service.

topicById.getComments();

Ci-dessus ne chargera la liste de commentaires que si votre session de veille prolongée est active, ce que je suppose que vous avez fermé dans votre service.

Donc, vous devez obtenir la liste de commentaires avant de fermer la session.

0
aditya lath