11

Foo looks has this in it :

@ManyToMany
private Set<User> favouritedBy;

while user has this:

@ManyToMany(mappedBy = "favouritedBy")
private Set<Foo> favourites  = new HashSet<Foo>();
public Set<Foo> getFavourites() {
  return favourite;
}

And fooService has this, with the lazyloaded collection being accessed while session is opened, via the tranactional method :

@Transactional(readOnly = true)
public Set<Foo> getFavourites(User user) {
user = dao.get(User.class, user.getId()); //the dao gets a session
Set<Foo> favourites = user.getFavourites();//but the session is not here and the exception is thrown?
return  favourties;
}

EDIT This fixes it, without using criteria :

Set<Foo> favourites = new HashSet<Foo>(user.getFavourites());

and this fixes it with criteria

Session session = sessionFactory.getCurrentSession();
final Criteria crit = session.createCriteria(Foo.class);
crit.setFetchMode("favourites", FetchMode.JOIN);
crit.add(Property.forName("id").eq(id));
return (Foo) crit.uniqueResult();
NimChimpsky
  • 46,453
  • 60
  • 198
  • 311

4 Answers4

12

The default FetchType in a ManyToMany is LAZY and the hibernate documentation for working with lazy associations clearly calls out this kind of access as an error. You can interact with lazily associated objects only while the session is still open. That portion of the documentation also provides alternatives to access such lazily associated members of an object . We prefer to specify the fetch mode as JOIN in the criteria used, in our applications

Edit:

Set<Foo> favourites = user.getFavourites();

The above statement doesn't actually return a set that contains all the Foo objects. It is just a proxy. The actual Foo objects are fetched only when the elements in the set are accessed like favorites.iterator() etc., This operation is clearly happening outside your getFavorites() method. But the @Transactional annotation on the getFavorites() method indicates that the session will be closed at the end of this method.

So, when methods are called on the favourites set, the session is already closed and hence the exception.

To address this, you should use a Criteria object to retrieve the user and specify the fetch type as JOIN so that the Foo objects are populated in the User object returned.

Vikdor
  • 23,934
  • 10
  • 61
  • 84
  • yeah I thought the session is still open though thats the issue as the method is transactional and uses dao which has session injected into it. – NimChimpsky Dec 02 '12 at 16:33
  • By the time your lazily-associated-set is copied to be available for the caller of `getFavorites()` method, the session would be already closed, as the scope of the transaction ends at the end of `getFavorites()` method. – Vikdor Dec 02 '12 at 16:38
  • I don't understand at all. "scope of the transaction ends at the end of getFavorites()" thats when i want it to end, I have called user.getFavourties while session is open, it will load from db the data and populate a hashset ? (well obviously it doesn't and I am wrong, I soemtimes wish I never bothered with hibernate) – NimChimpsky Dec 02 '12 at 16:40
  • Please see the update where I tried to explain what exactly happens. – Vikdor Dec 02 '12 at 16:46
  • @Transactional annotation guarantees that session is open until method ends, it you should be able to use lazy collections here – hoaz Dec 02 '12 at 16:46
  • @hoaz, true, but if you start accessing the collection / set outside this method, then the proxy that is actually returned by the `user.getFavorites()` method doesn't have a valid session to fetch them from the data store. – Vikdor Dec 02 '12 at 16:47
  • yeah, that is why i asked him to post stack trace – hoaz Dec 02 '12 at 16:49
  • @hoaz this answer explains it thanks. Although it seems somewhat weird to me, I thought the lazyload would happen on the user.getFav() not on manipulating the actual collection itself. – NimChimpsky Dec 02 '12 at 16:52
  • 1
    I can just do "Set favourites = new HashSet(user.getFavourites());" – NimChimpsky Dec 02 '12 at 17:15
  • Forcing the set to be traversed within the session boundaries, nice! – Vikdor Dec 02 '12 at 17:18
  • still seems a bit ugly tbh, and wastes cycles, I will have a go at creating the crieria with fetch type join, but it looks complex too. – NimChimpsky Dec 02 '12 at 17:20
  • Thanks a lot! Your info helped me to figure out that the only missing thing at my Column definition was @Fetch(FetchMode.JOIN) above the @ManyToMany(fetch = FetchType.LAZY) @JoinTable –  Nov 29 '20 at 13:31
9

There are two solutions.

  1. Don't use lazy load.

    Set lazy=false in XML or Set @OneToMany(fetch = FetchType.EAGER) In annotation.

  2. Use lazy load.

    Set lazy=true in XML or Set @OneToMany(fetch = FetchType.LAZY) In annotation.

    and add filter in your web.xml

     <listener>
         ...
     </listener>
     <filter>
         <filter-name>hibernateFilter</filter-name>
         <filter-class>
             org.springframework.orm.hibernate4.support.OpenSessionInViewFilter
         </filter-class>
         <init-param>
             <param-name>sessionFactoryBeanName</param-name>
             <param-value>mySessionFactory</param-value> 
         </init-param>
     </filter>
     <filter-mapping>
         <filter-name>hibernateFilter</filter-name>
         <url-pattern>/*</url-pattern>
     </filter-mapping>
     <servlet>
         ...
     </servlet>
    

And <param-value>mySessionFactory</param-value> is your sessionFacory bean name that defined in applicationContext.xml

saneryee
  • 3,239
  • 31
  • 22
  • 1
    This article discusses these proposed solutions: https://vladmihalcea.com/the-best-way-to-handle-the-lazyinitializationexception/ – olivmir Feb 21 '18 at 09:36
  • OpenInView is a bad solution as a first before any other available. There are lot more ways to fix this that we should use before this anti-pattern –  Nov 29 '20 at 13:32
2

Yes, the object should be accessed in the transactional context otherwise the operation will throw a LazyInitializationException.

geisterfurz007
  • 5,292
  • 5
  • 33
  • 54
0

If you use any of @...Many... relationships along with Fetch type "Lazy" and you're getting LazyInitializationException - that means you got OpenInView turned off, that is good.

To avoid both LazyInitializationException and turning OIV (that makes Hibernate session open longer than in most cases needed) - ensure you specified @Fetch(FetchMode.JOIN) on the issuing column.

Example: Before:

@ManyToMany(fetch = FetchType.LAZY)
private Set<Kek> keks;

After:

@Fetch(FetchMode.JOIN)
@ManyToMany(fetch = FetchType.LAZY)
private Set<Kek> keks;

This way you will force Join Fetch type that will (very simply talking) provide correct query with required linked entities Joined, not forcing you to use Eager fetch.