0

I am trying to fetch user's profile data post login (from login success filter) but I am seeing an exception for Lazy loading the data. Please see the following sample code:

AuthenticationSuccessHandler.java

@Component
public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Autowired
    private UserService userService;

    @Autowired
    private Gson gson;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws ServletException, IOException {

        User user = (User) authentication.getPrincipal();
        UserLoginResponseDto userLoginResponseDto = userService.login(user.getUsername());

        response.setStatus(HttpStatus.OK.value());
        response.setContentType("application/json; charset=UTF-8");
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.getWriter().println(gson.toJson(userLoginResponseDto));
        response.getWriter().flush();

        clearAuthenticationAttributes(request);
    }
}

UserService.java

public class UserService implements UserDetailsService, TokenService {

    @Autowired
    private UserRepository userRepository;

    @Transactional(readOnly = true)
    public UserLoginResponseDto login(String email) {
        Optional<UserModel> userOptional = userRepository.findByEmailIgnoreCase(email);
        UserModel userModel = userOptional.get();
        UserLoginResponseDto userLoginResponseDto = userModel.toUserLoginResponseDto();
        return userLoginResponseDto;
    }
}

UserModel.java

public class UserModel {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(nullable = false, unique = true, updatable = false)
    private UUID id;


    [A FEW MORE FIELDS]

    @Column(length = 256, nullable = false, unique = true, updatable = false)
    private String email;

    @OneToMany(cascade = { CascadeType.ALL })
    private List<RoleModel> roleModels;

    public UserLoginResponseDto toUserLoginResponseDto() {
        return new UserLoginResponseDto().setId(id).setEmail(email).setRoles(roleModels);
    }
}

UserLoginResponseDto.java

public class UserLoginResponseDto {

    private UUID id;
    private String email;
    private List<RoleModel> roles;

}

When an object of type UserLoginResponseDto is serialized in AuthenticationSuccessHandler, I see the following error message -

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

QQ - How do I resolve this correctly without employing any of the following techniques?

  • [ANTIPATTERN] Open-In-View
  • [ANTIPATTERN] hibernate.enable_lazy_load_no_trans
  • FetchType.EAGER
Rahil Parikh
  • 89
  • 1
  • 8

1 Answers1

1

Your problem is that you're passing the actual lazy List into setRoles, which doesn't trigger the full load. This indicates (immediately) that while you've separated your top-level database class from your top-level DTO, it's a "shallow" separation, which doesn't fully materialize the values. You haven't shown whether RoleModel is an entity or an embeddable, and that matters.

So the first step is to copy the items into a non-JPA form. If RoleModel embeddable (essentially a POJO), this could be as simple as setRoles(new ArrayList<>(roles)). Otherwise, you need a nested DTO, and at that point might consider something like MapStruct.

In either case, though, you're likely to run into the N+1 problem. You in fact do want an eager fetch in this case, and that's what JPA entity graph is for. You can tell Spring Data to fetch the list eagerly only when you want it, and this is a perfect example of when to do that.

chrylis -cautiouslyoptimistic-
  • 75,269
  • 21
  • 115
  • 152
  • `RoleModel` is an entity as we can see from this mapping: `@OneToMany(cascade = { CascadeType.ALL }) private List roleModels; ` – SternK Dec 26 '20 at 08:10