4

I have two entities with many-to-many relationship.

in class Role:

@ManyToMany(mappedBy = "roles")
private Set<User> users = new HashSet<>();

and in class User:

@ManyToMany
@JoinTable(name = "role_user", joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();

And I get the exception:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: User.roles, could not initialize proxy - no Session

When I add fetch = FetchType.EAGER, I get another exception:

java.lang.StackOverflowError: null

and cycle between Role and User.

How can I resolve this problem? I saw similar questions on Stackoverflow, but I didn't find real worked solution for me.

UPD: Where the exception gets thrown:

@Service
public class UserAuthenticationProvider implements AuthenticationProvider {
    ...

    @Override
    @Transactional
    public Authentication authenticate(final Authentication authentication) {
        final String login = authentication.getName();
        final String password = authentication.getCredentials().toString();

        final User user = userRepository.findByLogin(login);
        if (user != null && passwordEncoder.matches(password, user.getPassword())) {
            return new UsernamePasswordAuthenticationToken(login, password,
                    user.getRoles()); // problem here
        } else {
            return null;
        }
    }

    ...
Daria Pydorenko
  • 1,754
  • 2
  • 18
  • 45
  • I am not convinced that the problem is in the declarations you have presented. Please present a [mcve] with which one or both of the exceptions you report can be reproduced. – John Bollinger Oct 18 '17 at 13:13

3 Answers3

3

Hibernate already tells you why:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: User.roles, could not initialize proxy - no Session

public List<Role> getRolesForUserId(int id){
    UserEntity user = userRepository.findUserById(1)
    user.getRoles()
}

This will cause an exception, because you trying to get the lazy fetched user roles without an active hibernate session.

@Transactional is your friend. The @Transactional annotation will create a hibernate session with a certain scope (e.g. Method)

@Transactional 
public List<Role> getRolesForUserId(int id){
    UserEntity user = userRepository.findUserById(1)
    user.getRoles()
}

This will work, because hibernate keeps the same session open for the scope of this method.

Herr Derb
  • 4,977
  • 5
  • 34
  • 62
  • It didn't help me. I added `@Transactional` annotation and `@EnableTransactionManagement`, but nothing changed. If I configure `HibernateTransactionManager` and `SessionFactory` with beans, I have an exception about `bean named 'entityManagerFactory' that could not be found.` What else can I try? – Daria Pydorenko Oct 18 '17 at 14:32
  • I'm agree with the @Herr's solution. seems like your collection is detached from the session. also in model classes you can update the annotations. In class Role update the annotation `@ManyToMany(fetch = FetchType.LAZY, mappedBy = "roles")` and in the class User update it to @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL). – Neero Oct 18 '17 at 16:38
  • I updated annotations, but it didn't help. I tried to use this answer: https://stackoverflow.com/a/30884155/8253837 and added `@PersistenceContext private EntityManager em;` in the class with `@Transactional` method and now I have `java.lang.StackOverflowError`exception. Also my UserRepository extends CrudRepository, if it matters; but I created separeted class for `@Transactional` method that uses UserRepository. – Daria Pydorenko Oct 19 '17 at 06:59
  • You should not need any extra configuration for this. Can you add the code where the actual exception gets thrown? – Herr Derb Oct 19 '17 at 07:11
  • Do you use the annotation `@SpringBootApplication`? – Herr Derb Oct 19 '17 at 07:13
  • So do you know what else can I try? – Daria Pydorenko Oct 21 '17 at 12:04
2

Using Spring JPA try the keyword "JOIN FETCH" on your JPQL you can handle lazy initialization exception. Example

@Query("SELECT u FROM User u JOIN FETCH u.roles r WHERE r.roleId = :roleId AND u.userId=:userId")
User findUserById(@Param("roleId") String roleId, @Param("userId") String userId);
joel
  • 93
  • 2
  • 12
1

It looks like @Transactional doesn't work for an overridden method of AuthenticationProvider. I should have created a separate service to handle this issue.

A similar answer here: https://stackoverflow.com/a/23652569/8253837

Daria Pydorenko
  • 1,754
  • 2
  • 18
  • 45