Pour une réponse, faites défiler jusqu'à la fin de la ...
Le problème de base est le même que celui demandé plusieurs fois. J'ai un programme simple avec deux événements POJO et un utilisateur - où un utilisateur peut avoir plusieurs événements.
@Entity
@Table
public class Event {
private Long id;
private String name;
private User user;
@Column
@Id
@GeneratedValue
public Long getId() {return id;}
public void setId(Long id) { this.id = id; }
@Column
public String getName() {return name;}
public void setName(String name) {this.name = name;}
@ManyToOne
@JoinColumn(name="user_id")
public User getUser() {return user;}
public void setUser(User user) {this.user = user;}
}
L'utilisateur:
@Entity
@Table
public class User {
private Long id;
private String name;
private List<Event> events;
@Column
@Id
@GeneratedValue
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
@Column
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@OneToMany(mappedBy="user", fetch=FetchType.LAZY)
public List<Event> getEvents() { return events; }
public void setEvents(List<Event> events) { this.events = events; }
}
Remarque: Ceci est un exemple de projet. I vraiment veux utiliser la récupération paresseuse ici.
Nous devons maintenant configurer spring et hibernate et disposer d’un simple basic-db.xml pour le chargement:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="myDataSource" class="org.Apache.commons.dbcp.BasicDataSource"
destroy-method="close" scope="thread">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://192.168.1.34:3306/hibernateTest" />
<property name="username" value="root" />
<property name="password" value="" />
<aop:scoped-proxy/>
</bean>
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread">
<bean class="org.springframework.context.support.SimpleThreadScope" />
</entry>
</map>
</property>
</bean>
<bean id="mySessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" scope="thread">
<property name="dataSource" ref="myDataSource" />
<property name="annotatedClasses">
<list>
<value>data.model.User</value>
<value>data.model.Event</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">create</prop>
</props>
</property>
<aop:scoped-proxy/>
</bean>
<bean id="myUserDAO" class="data.dao.impl.UserDaoImpl">
<property name="sessionFactory" ref="mySessionFactory" />
</bean>
<bean id="myEventDAO" class="data.dao.impl.EventDaoImpl">
<property name="sessionFactory" ref="mySessionFactory" />
</bean>
</beans>
Note: J'ai joué avec les CustomScopeConfigurer et SimpleThreadScope, mais cela n'a rien changé.
J'ai un simple dao-impl (coller seulement le userDao - le EventDao est à peu près le même - sauf avec la fonction "listWith":
public class UserDaoImpl implements UserDao{
private HibernateTemplate hibernateTemplate;
public void setSessionFactory(SessionFactory sessionFactory) {
this.hibernateTemplate = new HibernateTemplate(sessionFactory);
}
@SuppressWarnings("unchecked")
@Override
public List listUser() {
return hibernateTemplate.find("from User");
}
@Override
public void saveUser(User user) {
hibernateTemplate.saveOrUpdate(user);
}
@Override
public List listUserWithEvent() {
List users = hibernateTemplate.find("from User");
for (User user : users) {
System.out.println("LIST : " + user.getName() + ":");
user.getEvents().size();
}
return users;
}
}
Je reçois l'org.hibernate.LazyInitializationException - échec de l'initialisation paresseuse d'une collection de rôle: data.model.User.events, aucune session ou session n'a été fermée à la ligne avec user.getEvents (). Size () ;
Et enfin, voici la classe de test que j'utilise:
public class HibernateTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("basic-db.xml");
UserDao udao = (UserDao) ac.getBean("myUserDAO");
EventDao edao = (EventDao) ac.getBean("myEventDAO");
System.out.println("New user...");
User user = new User();
user.setName("test");
Event event1 = new Event();
event1.setName("Birthday1");
event1.setUser(user);
Event event2 = new Event();
event2.setName("Birthday2");
event2.setUser(user);
udao.saveUser(user);
edao.saveEvent(event1);
edao.saveEvent(event2);
List users = udao.listUserWithEvent();
System.out.println("Events for users");
for (User u : users) {
System.out.println(u.getId() + ":" + u.getName() + " --");
for (Event e : u.getEvents())
{
System.out.println("\t" + e.getId() + ":" + e.getName());
}
}
((ConfigurableApplicationContext)ac).close();
}
}
et voici l'exception:
1621 [main] ERREUR org.hibernate.LazyInitializationException - échec de l'initialisation paresseuse d'une collection de rôle: data.model.User.events, aucune session ou session n'a été fermée Org.hibernate.LazyInitializationException: n'a pas réussi à initialiser paresseusement une collection de rôle: data.model.User.events, aucune session ou session n'a été fermée à org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException (AbstractPersistentCollection.Java:380) à org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected (AbstractPersistentCollection.Java:372) .PersistentBag.size (PersistentBag.Java:248) À data.dao.impl.UserDaoImpl.listUserWithEvent (UserDaoImpl.Java:38) à. HibernateTest.main (HibernateTest.main (HibernateTest.Java:44) Exception dans le fil "principal" org.hibernate.LazyIniti alizationException: échec de l'initialisation paresseuse d'une collection de rôle: data.model.User.events, aucune session ou session n'a été fermée à org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException (Abstract, AbstractPersistentCollection.Java:380) [. .] à org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected (AbstractPersistentCollection.Java:372) à org.hibernate.collection.AbstractPersistentCollection.readSize (AbstractPersistentCollection.read). .collection.PersistentBag.size (PersistentBag.Java:248) à data.dao.impl.UserDaoImpl.listUserWithEvent (UserDaoImpl.Java:38) à HibernateTest.main (HibernateTest.main (HibernateTest.Java:44) )
Les choses ont essayé mais n'ont pas fonctionné:
// objet de l'étendue Étendue threadScope = new SimpleThreadScope (); ConfigurableListableBeanFactory beanFactory = ac.getBeanFactory (); beanFactory.registerScope ("demande", threadScope ); ac.refresh (); ...
... Transaction tx = ((UserDaoImpl) udao) .getSession (). BeginTransaction (); Tx.begin (); Utilisateurs = udao .listUserWithEvent (); ...
public List listUserWithEvent () { SessionFactory sf = hibernateTemplate.getSessionFactory (); Session s = sf.openSession (); Transaction tx = s.beginTransaction (); tx.begin (); Répertorier les utilisateurs = hibernateTemplate.find ("from User"); pour (Utilisateur: utilisateurs) { System.out.println ("LIST:" + user.getName () + ":"); User.getEvents (). Size (); } tx.commit (); renvoyer des utilisateurs; }
Je suis vraiment à court d'idées maintenant. De plus, l’utilisation de listUser ou listEvent fonctionne parfaitement.
Avancer:
Grâce à Thierry, j'ai fait un pas de plus (je pense). J'ai créé la classe MyTransaction et j'y ai fait tout mon travail, en commençant par le printemps. Le nouveau principal ressemble à ceci:
public static void main(String[] args) {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("basic-db.xml");
// getting dao
UserDao udao = (UserDao) ac.getBean("myUserDAO");
EventDao edao = (EventDao) ac.getBean("myEventDAO");
// gettting transaction template
TransactionTemplate transactionTemplate = (TransactionTemplate) ac.getBean("transactionTemplate");
MyTransaction mt = new MyTransaction(udao, edao);
transactionTemplate.execute(mt);
((ConfigurableApplicationContext)ac).close();
}
Malheureusement, il existe maintenant un pointeur nul Exception @: user.getEvents (). Size (); (dans le daoImpl).
Je sais qu'il ne devrait pas être nul (ni de la sortie dans la console ni de la présentation de la base de données).
Voici la sortie de la console pour plus d'informations (j'ai fait une vérification sur user.getEvent () == null et imprimé "EVENT is NULL"):
Nouvel utilisateur ... Hibernate: insérer dans l'utilisateur (nom) valeurs (?) Hibernate: insérer dans utilisateur (nom) valeurs (?) Hibernate : insérer dans Event (nom, user_id) valeurs (?,?) Hibernate: insérer dans Event (nom, user_id) valeurs (?,?) Hibernate: insérer dans Event (nom, user_id ) valeurs (?,?) Liste des utilisateurs: Hibernate: sélectionnez user0_.id comme id0_, user0_.name comme nom0_ dans Utilisateur user0 _ 1: Utilisateur1 2: Utilisateur2 Liste des événements: Hibernate: sélectionnez event0_.id comme id1_, event0_.name comme nom1_, event0_.user_id comme utilisateur3_1_ à partir de l'événement event0 _ 1: Anniversaire1 pour 1: User1 2: Birthday2 pour 1: User1 3: Mariage pour 2: User2 Hibernate: sélectionnez user0_.id comme id0_, user0_.name comme nom0_ de l'utilisateur user0 _. .] Evénements pour les utilisateurs 1: User1 - EVENT est NULL 2: User2 - EVENT est NULL
Vous pouvez obtenir le projet exemple à l'adresse http://www.gargan.org/code/hibernate-test1.tgz (il s'agit d'un projet Eclipse/maven)
La solution (pour les applications en console)
Il existe en fait deux solutions à ce problème, selon votre environnement:
Pour une application console, vous avez besoin d'un modèle de transaction qui capture la logique de la base de données active et prend en charge la transaction:
public class UserGetTransaction implements TransactionCallback{
public List users;
protected ApplicationContext context;
public UserGetTransaction (ApplicationContext context) {
this.context = context;
}
@Override
public Boolean doInTransaction(TransactionStatus arg0) {
UserDao udao = (UserDao) ac.getBean("myUserDAO");
users = udao.listUserWithEvent();
return null;
}
}
Vous pouvez l'utiliser en appelant:
TransactionTemplate transactionTemplate = (TransactionTemplate) context.getBean("transactionTemplate");
UserGetTransaction mt = new UserGetTransaction(context);
transactionTemplate.execute(mt);
Pour que cela fonctionne, vous devez définir la classe de modèle pour spring (c'est-à-dire dans votre fichier basic-db.xml):
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>
Une autre solution (possible)
merci andi
PlatformTransactionManager transactionManager = (PlatformTransactionManager) applicationContext.getBean("transactionManager");
DefaultTransactionAttribute transactionAttribute = new DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED);
transactionAttribute.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
TransactionStatus status = transactionManager.getTransaction(transactionAttribute);
boolean success = false;
try {
new UserDataAccessCode().execute();
success = true;
} finally {
if (success) {
transactionManager.commit(status);
} else {
transactionManager.rollback(status);
}
}
La solution (pour les servlets)
Les servlets ne sont pas un gros problème. Lorsque vous avez une servlet, vous pouvez simplement démarrer et lier une transaction au début de votre fonction et la dissocier à la fin:
public void doGet(...) {
SessionFactory sessionFactory = (SessionFactory) context.getBean("sessionFactory");
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
// Your code....
TransactionSynchronizationManager.unbindResource(sessionFactory);
}
Je pense que vous ne devriez pas utiliser les méthodes transactionnelles de session hibernate, mais laissez spring le faire.
Ajoutez ceci à votre conf de printemps:
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="mySessionFactory" />
</bean>
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="txManager"/>
</bean>
et puis je modifierais votre méthode de test pour utiliser le modèle de transaction printanière:
public static void main(String[] args) {
// init here (getting dao and transaction template)
transactionTemplate.execute(new TransactionCallback() {
@Override
public Object doInTransaction(TransactionStatus status) {
// do your hibernate stuff in here : call save, list method, etc
}
}
}
en remarque, les associations @OneToMany sont paresseuses par défaut, vous n'avez donc pas besoin de l'annoter. (@ * ToMany sont LAZY par défaut, @ * ToOne sont EAGER par défaut)
EDIT: voici maintenant ce qui se passe du point de vue de l'hibernation:
... idem avec toutes les opérations de sauvegarde ...
puis chargez tous les utilisateurs (la requête "from Users")
Voici quelques points pour améliorer votre code:
Donc, pour répondre au point ci-dessus, effectuez la sauvegarde dans une transaction et le chargement dans une autre:
public static void main(String[] args) {
// init here (getting dao and transaction template)
transactionTemplate.execute(new TransactionCallback() {
@Override
public Object doInTransaction(TransactionStatus status) {
// save here
}
}
transactionTemplate.execute(new TransactionCallback() {
@Override
public Object doInTransaction(TransactionStatus status) {
// list here
}
}
}
ou définir les deux côtés:
...
event1.setUser(user);
...
event2.setUser(user);
...
user.setEvents(Arrays.asList(event1,event2));
...
(N'oubliez pas non plus d'adresser les points d'amélioration du code ci-dessus, Définir pas la liste, saisie de la collection)
Dans le cas d'une application Web, il est également possible de déclarer un filtre spécial dans web.xml, qui fera session par demande:
<filter>
<filter-name>openSessionInViewFilter</filter-name>
<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>openSessionInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Ensuite, vous pouvez charger vos données à tout moment pendant la demande.
Je suis venu chercher un indice concernant un problème similaire. J'ai essayé la solution mentionnée par Thierry et cela n'a pas fonctionné. Après cela, j'ai essayé ces lignes et cela a fonctionné:
SessionFactory sessionFactory = (SessionFactory) context.getBean("sessionFactory");
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
En effet, ce que je fais est un processus de traitement par lots qui doit exploiter les gestionnaires/services d’existence Spring. Après avoir chargé le contexte et fait quelques invocations, j’ai fondé le fameux problème "Impossible d’initialiser paresseusement une collection". Ces 3 lignes l'ont résolu pour moi.
Le problème est que votre dao utilise une session de veille prolongée mais que la charge paresseuse de user.getName (je suppose que c'est là où elle est lancée) se produit en dehors de cette session - pas dans une session du tout ou dans une autre session. En général, nous ouvrons une session d'hibernation avant/ nous passons des appels DAO et nous ne la fermons pas avant d'avoir terminé toutes les tâches paresseuses. Les demandes Web sont généralement encapsulées dans une grande session afin que ces problèmes ne se produisent pas.
En règle générale, nous avons intégré nos appels DAO et paresseux dans un SessionWrapper. Quelque chose comme ce qui suit:
public class SessionWrapper {
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
this.hibernateTemplate = new HibernateTemplate(sessionFactory);
}
public <T> T runLogic(Callable<T> logic) throws Exception {
Session session = null;
// if the session factory is already registered, don't do it again
if (TransactionSynchronizationManager.getResource(sessionFactory) == null) {
session = SessionFactoryUtils.getSession(sessionFactory, true);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
}
try {
return logic.call();
} finally {
// if we didn't create the session don't unregister/release it
if (session != null) {
TransactionSynchronizationManager.unbindResource(sessionFactory);
SessionFactoryUtils.releaseSession(session, sessionFactory);
}
}
}
}
De toute évidence, la SessionFactory est la même que la SessionFactory qui a été injectée dans votre dao.
Dans votre cas, vous devez envelopper le corps entier de listUserWithEvent dans cette logique. Quelque chose comme:
public List listUserWithEvent() {
return sessionWrapper.runLogic(new Callable<List>() {
public List call() {
List users = hibernateTemplate.find("from User");
for (User user : users) {
System.out.println("LIST : " + user.getName() + ":");
user.getEvents().size();
}
}
});
}
Vous devrez injecter l'instance SessionWrapper dans vos daos.
Intéressant!
J'ai rencontré le même problème dans la méthode de gestionnaire @RequestMapping d'un contrôleur .. ... La solution simple consistait à ajouter une annotation @Transactional à la méthode de gestionnaire afin que la session reste ouverte pendant toute la durée d'exécution du corps de la méthode.
La solution la plus simple à mettre en œuvre:
Dans le cadre de la session [à l'intérieur de l'API annotée avec @Transactional], procédez comme suit:
si A avait une liste <B> qui est chargée paresseusement, appelez simplement une API qui s'assure que la liste est chargée
Quelle est cette API?
taille(); API de la classe List.
Donc, tout ce qui est nécessaire est:
Logger.log (a.getBList.size ());
Ce simple appel de la consignation de la taille permet d’obtenir la liste complète avant de calculer la taille de la liste. Maintenant, vous n'obtiendrez pas l'exception!
Ce qui a fonctionné pour nous chez JBoss est la solution n ° 2 tirée de ce site de Java Code Geeks .
Web.xml:
<filter>
<filter-name>ConnectionFilter</filter-name>
<filter-class>web.ConnectionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ConnectionFilter</filter-name>
<url-pattern>/faces/*</url-pattern>
</filter-mapping>
ConnectionFilter:
import Java.io.IOException;
import javax.annotation.Resource;
import javax.servlet.*;
import javax.transaction.UserTransaction;
public class ConnectionFilter implements Filter {
@Override
public void destroy() { }
@Resource
private UserTransaction utx;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
utx.begin();
chain.doFilter(request, response);
utx.commit();
} catch (Exception e) { }
}
@Override
public void init(FilterConfig arg0) throws ServletException { }
}
Peut-être que cela fonctionnerait avec le printemps aussi.