Dani
Dani

Reputation: 4111

Clear Hibernate PersistentSet in persistent-to-detached object

The scenario:

Two mapped classes.

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

    ...

    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
    private Set<UserFriend> friends = new HashSet<>();

    ...

}

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

    ...

    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false)
    private User user;

    ...

}

The most commonly approach is get all friends, but in one special case I want to get the accepted friends only. To get this approach, in my DAO method I'm doing the next.

    Criteria criteria = this.getCurrentSession().createCriteria(User.class)
            .createAlias("userAccount", "ua")
                .add(Restrictions.like("ua.email", email).ignoreCase())
            .createCriteria(
                    "friends",
                    "f",
                    JoinType.LEFT_OUTER_JOIN,
                    Restrictions.eq("accept", true))
            .setCacheRegion(CACHE_REGION)
            .setCacheable(true);

    User user = (User) criteria.uniqueResult();

This is working fine.

Hibernate: select ... from user this_ 
left outer join user_friend fr2_ on this_.id=fr2_.user_id and ( fr2_.accept=? ) 
left outer join user user9_ on fr2_.user_id=user9_.id 
inner join user_account ua1_ on this_.id=ua1_.user_id where lower(ua1_.email) like ?

Problem comes when user.getFriends() is invoked from another site inside the @Transactional scope, then Lazy initialization is performed and override the current Friends Set status.

To avoid this, after load, I'm detaching the user object from the Hibernate Session.

super.getCurrentSession().evict(user);

But, later, although Friends Set has been initialized before, I get one LazyInitializationException and lost the Friend Set.

How could I avoid this and keep the Friend Set data avoiding Lazy initialization? (I can't change EAGER instead of LAZY).

The solution based in the @Maarten response

@FilterDef(
        name = "friendStatus",
        parameters = {@ParamDef(name = "status", type = "boolean")})
public class User implements Serializable {

    ...

    @OneToMany(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    @Filter(name = "friendStatus", condition = "accept = :status")
    private Set<UserFriend> friends = new HashSet<>();

    ...
}

In the DAO method.

public User getUserByEmailWithValidFriends(String email) {

        super.getCurrentSession()
                .enableFilter("friendStatus").setParameter("status", true);

        Criteria criteria = super.getCurrentSession()
                .createCriteria(User.class)
                .createAlias("userAccount", "ua")
                .add(Restrictions.like("ua.email", email).ignoreCase())
                .setCacheRegion(CACHE_REGION)
                .setCacheable(true);

        return criteria.uniqueResult();
}

Works fine. :)

Upvotes: 0

Views: 571

Answers (1)

Maarten Winkels
Maarten Winkels

Reputation: 2417

There are two (or three) options:

  1. collection filter
  2. use a query to get the collection
  3. do the filtering in memory

First option is most sophisticated. You have to enable the filter on the hibernate session programmatically when you need it. From then on, the collection will operate as if only accepted friends are in it, no need to write a specific query.

Second option is to write a query which just fetches the accepted friends for a user and returns as list. This list is neither attached to the user nor to the session (although the users in the list are), so you won't have lazy initialization problems. This it's probably easiest to implement.

Third is obvious. Fetch all friends and filter in memory. This could be a performance hit if users have many non-accepted friends.

Upvotes: 1

Related Questions