0
votes

I have a problem when trying to delete from a lazy fetch list in an object. I have a User object and an Event object. A User can attend to an Event and this is stored in both objects.

The User object:

@Entity
@Table(name = "User")
public class User implements Serializable {

    @ManyToMany(fetch = FetchType.LAZY, mappedBy = "attendingUsers")
    private List<Event> attendingEvents = new ArrayList<Event>();
}

The Event object:

@Entity
@Table(name = "Event")
public class Event implements Serializable {

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "Event_User",
        joinColumns = @JoinColumn(name = "event_id", nullable = false, updatable = false), 
        inverseJoinColumns = @JoinColumn(name = "user_id", nullable = false, updatable = false))
    List<User> attendingUsers = new ArrayList<User>();
}

Of course there are Setters and Getters for these lists. Now I want to implement the possibility to unattend these events as a user. Therefore I tried the following:

public void unattend(Event e, User u){
    List<User> attendingUsers = getAttendingUsers(e.getId());
    List<Event> attendingEventsForUser = userService.getEvents(u.getId());
    if(attendingUsers.contains(u)){
        e.getAttendingUsers().clear();
        u.getAttendingEvents().clear();
        attendingUsers.remove(u);
        attendingEventsForUser.remove(e);
        e.getAttendingUsers().addAll(attendingUsers);
        u.getAttendingEvents().addAll(attendingEventsForUser);
        update(e);
        userDAO.update(u);
    }
}

The exception I get is the following:

07.01.2014 15:52:20 org.apache.catalina.core.StandardWrapperValve invoke SCHWERWIEGEND: Servlet.service() for servlet [appServlet] in context with path [/tripitude] threw exception [Request processing failed; nested exception is org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: ac.tuwien.ase08.tripitude.entity.Event.attendingUsers, could not initialize proxy - no Session] with root cause org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: ac.tuwien.ase08.tripitude.entity.Event.attendingUsers, could not initialize proxy - no Session at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:566) at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:186) at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:545) at org.hibernate.collection.internal.PersistentBag.clear(PersistentBag.java:381) at ac.tuwien.ase08.tripitude.service.EventService.unattend(EventService.java:76) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317) at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150) at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:96) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:260) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204) at $Proxy64.unattend(Unknown Source)

Although I'm using methods to assure that the lazy fetch is executed:

public List<User> getAttendingUsers(Long id){
    Query query = sessionFactory.getCurrentSession().createQuery(
            "SELECT u FROM User AS u LEFT JOIN u.attendingEvents AS ae WHERE ae.id = :id");
    query.setParameter("id", id);

    List<User> l = (List<User>)query.list();

    return l;
}

and the other one:

    @Override
public List<Event> getEvents(Long id) {
    Query query = sessionFactory.getCurrentSession().createQuery(
            "SELECT e FROM Event AS e LEFT JOIN e.attendingUsers AS au WHERE au.id = :id");
    query.setParameter("id", id);

    List<Event> l = (List<Event>)query.list();

    return l;
}

I was thinking about deleting with a query but then again I couldn't think of the right code to do so :/

Help would be highly appreciated, thanks!

EDIT: I just realized I kept calling .getAttendingUsers() and .getAttendingEvents() while I haven't actually initialized those two. So now I skipped that part and updated my code to this:

public void unattend(Event e, User u){
    Query query = sessionFactory.getCurrentSession().createQuery(
            "SELECT u FROM User AS u LEFT JOIN FETCH u.attendingEvents AS ae WHERE ae.id = :id");
    query.setParameter("id", e.getId());
    List<User> attendingUsers = (List<User>)query.list();

    Query query2 = sessionFactory.getCurrentSession().createQuery(
            "SELECT e FROM Event AS e LEFT JOIN FETCH e.attendingUsers AS au WHERE au.id = :id");
    query2.setParameter("id", u.getId());

    List<Event> attendingEventsForUser = (List<Event>)query2.list();

    if(attendingUsers.contains(u)){
        attendingUsers.remove(u);
        attendingEventsForUser.remove(e);
        e.setAttendingUsers(attendingUsers);
        u.setAttendingEvents(attendingEventsForUser);
        update(e);
        userDAO.update(u);
    }
}

And I get a new exception:

07.01.2014 16:51:34 org.apache.catalina.core.StandardWrapperValve invoke SCHWERWIEGEND: Servlet.service() for servlet [appServlet] in context with path [/tripitude] threw exception [Request processing failed; nested exception is org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [ac.tuwien.ase08.tripitude.entity.Event#3]] with root cause org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [ac.tuwien.ase08.tripitude.entity.Event#3] at org.hibernate.engine.internal.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:697) at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:296) at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:241) at org.hibernate.event.internal.DefaultUpdateEventListener.performSaveOrUpdate(DefaultUpdateEventListener.java:55) at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:90) at org.hibernate.internal.SessionImpl.fireUpdate(SessionImpl.java:786) at org.hibernate.internal.SessionImpl.update(SessionImpl.java:778) at org.hibernate.internal.SessionImpl.update(SessionImpl.java:774) at ac.tuwien.ase08.tripitude.dao.HibernateDAO.update(HibernateDAO.java:43) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317) at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150) at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:96) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:260) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204) at $Proxy63.update(Unknown Source) at ac.tuwien.ase08.tripitude.service.EventService.update(EventService.java:41) at ac.tuwien.ase08.tripitude.service.EventService.unattend(EventService.java:84)

1
Is unattend() being called within the context of a transaction (it doesn't appear to be annotated @Transactional)? Is the EventService class annotated @Transactional?Will Keeling
unattend() ist called in the RestController responsible for the event (the call comes from an android application over a post request). The EventService is annotated with @Transactional(propagation= Propagation.REQUIRED, readOnly=false), while the method itself is not annotated in any way.Cr0w3

1 Answers

0
votes

You are trying to access the lazy collections outside of a transaction. You can either annotate the relevant method with @Transactional or use fetch joins:

SELECT e FROM Event AS e LEFT JOIN FETCH e.attendingUsers ...

Working inside a transaction is generally preferable, since you fetch the related entities only if you need to.

EDIT: the Exception probably originates from the if clause in the 'unattend' method, where you access the lists: e.getAttendingUsers(). I would move it from the controller into a transactional service, like the EventService.