Nightloewe
Nightloewe

Reputation: 1098

LazyInitializationException occurs when trying to access collection of loaded entity

I'm getting a LazyInitializationException when I'm trying to access the permissions collection of my User object. Exception message:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: dev.teamnight.nightweb.core.entities.User.permissions, could not initialize proxy - no Session

This are the important parts of the User class:

@Entity
@Table(name = "users")
@Inheritance(strategy = InheritanceType.JOINED)
public class User {

    // [...] Other variables
        @OneToMany
    @JoinTable(
            name = "user_groups",
            joinColumns = @JoinColumn(name = "groupId"),
            inverseJoinColumns = @JoinColumn(name = "userId")
            )
    private List<Group> groups = new ArrayList<Group>();

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user")
    @OrderBy("name ASC")
    private List<UserPermission> permissions;

    // [...] Getter Setter

    @Override
    public boolean hasPermission(String permission) {
        /**
         * Check for User
         * UserPermission has yes or no/neutral, no resolves to true, no/neutral to no, UserPermissions override all GroupPermissions, if no go to GroupPerms
         * (Groups are sorted by priority, highest priority first, all permissions to one ArrayList)
         * GroupPermission true allows, neutral ignores, false denies
         */
        UserPermission userPerm = this.permissions.stream()
                .filter(perm -> perm.getName().equals(permission))
                .filter(perm -> perm.getType() == Permission.Type.FLAG)
                .filter(perm -> perm.getAsBoolean())
                .findFirst()
                .orElse(null);

        if(userPerm != null) {
            return true;
        }

        boolean allow = false;

        List<GroupPermission> groupPermissions = new ArrayList<GroupPermission>();

        this.groups.forEach(group -> {
            groupPermissions.addAll(group.getPermissions().stream().filter(perm -> perm.getType() == Permission.Type.FLAG).collect(Collectors.toList()));
        });

        for(GroupPermission perm : groupPermissions) {
            Tribool bool = perm.getAsTribool();

            if(bool == Tribool.TRUE) {
                allow = true;
            } else if(bool == Tribool.FALSE) {
                return false;
            }
        }
        return allow;
    }

UserPermission.java:

@Entity
@Table(name = "user_permissions", uniqueConstraints = @UniqueConstraint(columnNames = {"userId", "name"}))
public class UserPermission extends Permission {

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

}

UserHelper.java:

    public <T extends User> T getByUsername(String username, Class<T> type) {
        Session session = this.factory().getCurrentSession();
        session.beginTransaction();

        Query<T> query = session.createQuery("FROM " + type.getCanonicalName() + " U WHERE U.username = :username", type);
        query.setParameter("username", username);

        T user = query.uniqueResult();
        session.getTransaction().commit();

        return user;
    }

I'm calling getByUsername() to receive the user from the database and then later I try to access the permissions using User.hasPermission("testperm") and then the exception above happens.

EDIT: Mention there is another association, so FetchType.EAGER or @Fetch(FetchMode.join) lead to another exception.

Upvotes: 2

Views: 1128

Answers (3)

Thorben Janssen
Thorben Janssen

Reputation: 3275

You have 2 options to fix the LazyInitializationException. You could use multiple JOIN FETCH clauses to initialize all required associations, or you could perform a query for each required association. I explained both in great details in my recent article about the 2 best options to fix Hibernate's MultipleBagFetchException.

Option 1 - Multiple JOIN FETCH clauses

For this option, you need to change the type of your to-many associations to Set instead of List. This prevents MulitpleBagFetchException when fetching multiple to-many associations. In the next step, you can use a query that JOIN FETCHes all required association, e.g.:

SELECT u FROM User u LEFT JOIN FETCH u.permissions LEFT JOIN FETCH u.groups

This is a good approach if the product created by your query is small. In this case, that means you only select a few User entities, and your permissions and groups associations only contain a few entities.

Option 2 - Multiple queries

Hibernate ensures that within a Session, each database record gets mapped to only 1 entity object. So, no matter how often you select the User entity with id 1, Hibernate will always map it to the same User entity object. You can use that initialize multiple associations without struggling with the MultipleBagFetchException. In this example, you could perform 2 queries. The 1st one gets User entities and initializes the permissions associations. The 2nd one gets User entities and initializes the groups associations.

SELECT u FROM User u LEFT JOIN FETCH u.permissions
SELECT u FROM User u LEFT JOIN FETCH u.groups

Upvotes: 2

JohannesB
JohannesB

Reputation: 2288

You may have run into the ancient problem that some people have solved with the Open-Session-In-View pattern or session-per-request pattern:

You can extend the scope of a Session and database transaction until the "view has been rendered". This is especially useful in servlet applications that utilize a separate rendering phase after the request has been processed. Extending the database transaction until view rendering, is achieved by implementing your own interceptor. However, this will be difficult if you rely on EJBs with container-managed transactions. A transaction will be completed when an EJB method returns, before rendering of any view can start. See the Hibernate website and forum for tips and examples relating to this Open Session in View pattern.

quote source

See also the same documentation (or Hibernate documentation Ch. 13 on Transactions and Concurrency for this and other patterns like:

session-per-conversation

NB.: I have seen these patterns work quite well in practice but some consider them an anti-pattern, read up on the arguments and alternatives (like the already mentioned in other answers: join fetch) in this article

P.S.: There is also extensive documentation from Hibernate on Fetching Strategies and performance

Upvotes: 0

Ali Hatami
Ali Hatami

Reputation: 144

I think (I'm not sure) changing the fetch type to EAGER would help, you can see the differences in the documentation.

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "user")
@OrderBy("name ASC")
private List<UserPermission> permissions;

Or use this annotation if you have multiple @*toMany

@LazyCollection(LazyCollectionOption.FALSE)

and remove fetch from the @*toMany. (link)

Upvotes: 0

Related Questions