I'm working on a Spring Boot application with spring-data-jpa (hibernate) and spring security. My user object looks like this:
@Entity(name = "User")
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public User implements UserDetails {
@Id
@Column(name = "ID", nullable = false, unique = true)
private UUID id;
@Column(name = "username", nullable = false, unique = true)
private String username;
@Column(name = "passwordHash", nullable = false)
private String passwordHash;
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "User_Roles",
joinColumns = [JoinColumn(name = "User_ID")],
inverseJoinColumns = [JoinColumn(name = "Role_ID")]
)
private Set<Role> roles;
// constructor, getters, setters
// hashCode() & equals() based on id
}
This object is fetched in our AuthenticationServletFilter and subsequently stored in the SecurityContext. As you can see, the roles collection is currently fetched in an eager fashion. The Role itself consists of ID and a name.
I'm now faced with the following issue:
- Since authentication happens on every request, I need to use the Hibernate Second-Level Cache to cache the user objects.
- Hibernate seems to refuse to do that because of the eagerly loaded
rolesfield. In the debug logs, I can see SQL queries which fetch the user object again and again for every authentication. - Loading the roles in a lazy fashion is difficult, because it means that the processing which occurs in the
AuthenticationServletFilterwould have to be@Transactional. If it's not, any code which accessesuser.rolesfrom theSecurityContextlater on will receive aLazyInitializationException. But marking a request filter as@Transactionalfeels wrong from a software architecture point of view.
What's the standard course of action for a situation like this? I could imagine that this is a pretty standard scenario.
I tried to make the roles collection lazy-loaded, but that resulted in LazyInitializationExceptions later down the line. Having it loaded eagerly fixes that, but seems to prevent the hibernate second level cache from caching and/or reusing the user object. Marking the AuthenticationServletFilter as @Transactional feels wrong.