Reputation: 1098
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
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
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.
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
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