org.mise en veille prolongée.LazyInitializationException: Comment utiliser correctement les Hibernate fonctionnalité de chargement différé
J'ai eu quelques problèmes pour charger une liste d'objets à partir de ma base de données en utilisant Hibernate et paresseux=true mode.
Espérons que quelqu'un pourra m'aider ici.
J'ai une classe simple appelé ici Compteutilisateur qui ressemble à ceci:
public class UserAccount {
long id;
String username;
List<MailAccount> mailAccounts = new Vector<MailAccount>();
public UserAccount(){
super();
}
public long getId(){
return id;
}
public void setId(long id){
this.id = id;
}
public String getUsername(){
return username;
}
public void setUsername(String username){
this.username = username;
}
public List<MailAccount> getMailAccounts() {
if (mailAccounts == null) {
mailAccounts = new Vector<MailAccount>();
}
return mailAccounts;
}
public void setMailAccounts(List<MailAccount> mailAccounts) {
this.mailAccounts = mailAccounts;
}
}
Je suis cartographie de cette classe dans Hibernate par le fichier de mappage:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="test.account.UserAccount" table="USERACCOUNT">
<id name="id" type="long" access="field">
<column name="USER_ACCOUNT_ID" />
<generator class="native" />
</id>
<property name="username" />
<bag name="mailAccounts" table="MAILACCOUNTS" lazy="true" inverse="true" cascade="all">
<key column="USER_ACCOUNT_ID"></key>
<one-to-many class="test.account.MailAccount" />
</bag>
</class>
</hibernate-mapping>
Comme vous pouvez le voir, le paresseux est définie sur "true" dans le sac de cartographie de l'élément.
L'enregistrement de données dans la base de données fonctionne correctement:
Chargement fonctionne aussi en appelant loadUserAccount(String username)
(voir code ci-dessous):
public class HibernateController implements DatabaseController {
private Session session = null;
private final SessionFactory sessionFactory = buildSessionFactory();
public HibernateController() {
super();
}
private SessionFactory buildSessionFactory() {
try {
return new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public UserAccount loadUserAccount(String username) throws FailedDatabaseOperationException {
UserAccount account = null;
Session session = null;
Transaction transaction = null;
try {
session = getSession();
transaction = session.beginTransaction();
Query query = session.createQuery("FROM UserAccount WHERE username = :uname").setParameter("uname", username));
account = (UserAccount) query.uniqueResult();
transaction.commit();
} catch (Exception e) {
transaction.rollback();
throw new FailedDatabaseOperationException(e);
} finally {
if (session.isOpen()) {
//session.close();
}
}
return account;
}
private Session getSession() {
if (session == null){
session = getSessionFactory().getCurrentSession();
}
return session;
}
}
Le problème est là: Quand j'ai accès à des éléments dans la liste "mailAccounts", j'obtiens l'exception suivante:
org.mise en veille prolongée.LazyInitializationException:
échoué paresseusement initialiser un
collection de rôle:
test.compte.Compteutilisateur.mailAccounts,
pas de session ou la session a été fermée
Je suppose que la raison de cette exception est que la session est fermé (je ne sais pas pourquoi et comment) et donc, Hibernate ne peut pas charger la liste.
Comme vous pouvez le voir, j'ai même retiré la session.close()
appel de la loadUserAccount()
méthode, mais la session semble toujours être fermé ou remplacée par une autre instance.
Si j'ai mis lazy=false
, alors tout fonctionne en douceur, mais ce n'est pas ce que je voulais parce que j'ai besoin de la fonction de chargement de données "à la demande" en raison de problèmes de performances.
Donc, si je ne peux pas être sûr que ma session est toujours valide après la méthode loadUserAccount(String username)
résilié, ce qui est le point d'avoir cette fonctionnalité et comment puis-je contourner cela?
Merci pour votre aide!
Ps: je suis un Hibernate débutant s'il vous plaît excuser mon noobishness.
Mise à jour: Voici mon hibernation config.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.password">foo</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/mytable</property>
<property name="hibernate.connection.username">user</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</property>
<!-- Auto create tables -->
<!-- <property name="hbm2ddl.auto">create</property>-->
<!-- Enable Hibernate's automatic session context management -->
<property name="current_session_context_class">thread</property>
<!-- Mappings -->
<mapping resource="test/account/SmampiAccount.hbm.xml"/>
<mapping resource="test/account/MailAccount.hbm.xml"/>
</session-factory>
</hibernate-configuration>
OriginalL'auteur Timo | 2011-04-29
Vous devez vous connecter pour publier un commentaire.
Chargement différé de travail ou pas n'a rien à voir avec les limites de transaction. Il exige seulement que la Séance soit ouverte.
Toutefois, lorsque la session est ouverte dépend de la façon dont vous avez réellement la SessionFactory, que vous n'avez pas de nous le dire! Il n'y a config passe derrière ce
SessionFactory.getCurrentSession()
ne fait! Si vous êtes à la laisser aller avec la version par défaut deThreadLocalSessionContext
et de ne rien faire pour gérer le cycle de vie, il ne fait défaut à la clôture de la session lorsque vous vous engagez. (D'où la conception commune que l'élargissement des limites des transactions est le "réparer" pour un chargement différé exception).Si vous vous gérer le propre session cycle de vie de
sessionFactory.openSession()
etsession.close()
vous serez en mesure de lazy load fine au sein de la session du cycle de vie, à l'extérieur des limites des transactions. Alternativement, vous pouvez fournir une sous-classe deThreadLocalSessionContext
qui gère la session du cycle de vie, avec les limites que vous désirez. Il y a aussi disponibles alternatives telles que la OpenSessionInView filtre qui peut être utilisé dans les applications web pour lier la session du cycle de vie pour le web demande de cycle de vie.edit: Vous pouvez également initialiser la liste à l'intérieur de la transaction si cela fonctionne pour vous. Je pense juste que mène vraiment maladroit Api quand vous avez besoin d'une nouvelle signature de la méthode d'une sorte de "drapeau" paramètre pour chaque niveau d'hydratation de votre entité. dao.getUser() dao.getUserWithMailAccounts() dao.getUserWIthMailAccountsAndHistoricalids() et ainsi de suite.
edit 2: Vous pouvez trouver cela utile pour les différentes approches de la durée de la session reste ouverte/la relation entre la session de la portée et de l'étendue de la transaction. (en particulier l'idée de session-par-requête-avec-détaché-objets vs session-par-la conversation.)
Cela dépend de vos besoins et de l'architecture juste comment grand d'une conversation est en réalité.
mise à Jour: en Remplacement de
factory.getCurrentSession()
avecfactory.openSession()
tout jamais de la fermeture de la session fonctionne, mais je me demande encore si c'est une bonne pratique.Que serait une rare approche, mais il pourrait être viable pour une application de bureau. Les séances ne sont pas thread-safe, donc vous devez être prudent sur la synchronisation de l'accès à partir asynchrone travailleurs. Difficile à dire sans rien savoir à propos de votre application! Il y a probablement une plus petite de la portée de la session du cycle de vie qui serait raisonnable. Rappelez-vous que lorsqu'une exception qui vient de sortir de la Séance, vous êtes censé pour en obtenir un nouveau. Si certaines mesures de gestion nécessaires, même si vous gardez la même référence. Et vous ne voulez probablement pas votre cache L1 croissance sans limite.
A ajouté une autre édition avec un lien pour vous.
Je suis la construction d'un multi-thread webapplication. Ne savais pas que la Session n'est pas thread-safe. Ça va rendre les choses encore un peu plus dur. De toute façon, merci beaucoup pour vos conseils. Je l'apprécie!
OriginalL'auteur Affe
La raison pour laquelle vous obtenez l'exception peut-être que la transaction vous chargez les données dans est fermé (et la séance avec elle), c'est à dire que vous travaillez à l'extérieur de la session. Chargement différé est particulièrement utile lorsque vous travaillez avec des entités en une seule session (ou à travers des sessions lorsqu'il est correctement employant un cache de second niveau).
Autant que je sache, vous pouvez dire à Hibernate pour ouvrir automatiquement une session pour le chargement paresseux, mais je n'avais pas l'utiliser pendant un certain temps et donc je dois regarder comment ça fonctionne de nouveau.
Malheureusement je n'ai pas accès au code que nous avons utilisé plus, mais peut-être que cet article peut vous aider: theserverside.com/news/1363571/Remote-Lazy-Loading-in-Hibernate (nous avons utilisé pour charger paresseux associations dans un client distant).
OriginalL'auteur Thomas
Vous avez besoin d'envelopper l'ensemble de vos processus au sein d'une transaction.
Donc au lieu de départ et de la validation de la transaction en loadUserAccount, le faire à l'extérieur.
Par exemple:
Habituellement, vous souhaitez envelopper votre transaction autour de l'ensemble de l'unité de travail. Je soupçonne que votre compréhension de transactions est un peu trop fine.
Ce type d'application, est-ce? Web app? Autonome?
Il est multi-thread, ajax basé sur le web application.
OriginalL'auteur Tazzy531