[오류/해결] LazyInitializationException: could not initialize proxy - no Session

이신영·2023년 9월 12일
0

오류 모음집

목록 보기
19/26
post-thumbnail

로그인을 구현하는 도중..

Lazy뭐시기... no.. Session?

    @Transactional
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Optional<UserEntity> userEntityOptional = userRepository.findByUsername(username);
        if (userEntityOptional.isPresent()) {
            UserEntity userEntity = userEntityOptional.get();

            Collection<GrantedAuthority> authorities = userEntity.getAuthorities();

            // CustomUser 객체를 생성하여 반환
            return new CustomUser(
                    userEntity.getUsername(),
                    userEntity.getPassword(),
                    authorities,
                    userEntity.getNickname()
            );
        } else {
            throw new UsernameNotFoundException("사용자명을 찾을 수 없습니다.");
        }
    }
public class UserEntity {

  
    @ElementCollection
    @CollectionTable(name = "authorities", joinColumns = @JoinColumn(name = "user_id"))
    @Column(name = "authority")
    private Collection<GrantedAuthority> authorities; // 권한 정보를 객체로 저장
}

UserEntity의 authorities칼럼에서 @ElementCollection은 지연로딩이 디폴트값이라서 발생한 문제가 아닐까 싶다.

영속성 컨텍스트나 프록시, JPA의 동작과정을 잘 모르고있기때문에 이번을 계기로 학습을 해야겠다는 생각이 들었다.


문제의 원인은 이렇다.

자연로딩 -> 데이터가 실제로 사용되는 시점에서 데이터 베이스 조회를 실행

  1. getAuthorities()를 호출함 -> 여기서 내가 이해하기로는, Collection<GrantedAuthority> authorities = userEntity.getAuthorities() 자체로는 db의 조회가 아닌 할당의 개념임
  2. 근데 얘는 지연로딩으로 설정되어있기때문에 UserEntity 객체를 만들어도 달라고할때 프록시(가짜객체)가 반환됨
  3. db조회는 프록시를 통해 지연로딩되어서 LazyInitializationException가 발생

즉, getAuthorities()를 호출하는 행위를 할 때 지연로딩 설정된 칼럼인 Authorities은 프록시를 반환하니 데이터가 (아직)없다 이거죠~

그렇다면 실제 db를 조회하기위해서는 프록시가 사용될 때 발생하겠지?


해결

Fetch 수정

    @ElementCollection(fetch = FetchType.EAGER)
    @CollectionTable(name = "authorities", joinColumns = @JoinColumn(name = "user_id"))
    @Column(name = "authority")
    private Collection<GrantedAuthority> authorities; // 권한 정보를 객체로 저장

이걸로는 잘 되던데 모든걸 조회하느라 성능이 안좋아지기도하고 많이 들어본 N+1 문제도 발생할 수 있다. 최소한으로 사용해야겠다..!

지연로딩 회피하기(프록시를 사용)

    @Transactional
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Optional<UserEntity> userEntityOptional = userRepository.findByUsername(username);

        if (userEntityOptional.isPresent()) {
            UserEntity userEntity = userEntityOptional.get();
            int authoritiesSize = userEntity.getAuthorities().size();

            UserDTO userDTO = new UserDTO();
            userDTO.setUsername(userEntity.getUsername());
            userDTO.setPassword(userEntity.getPassword());
            userDTO.setNickname(userEntity.getNickname());
            userDTO.setAuthorities(userEntity.getAuthorities());

            // CustomUser 객체를 생성하여 반환
            return new CustomUser(
                    userDTO.getUsername(),
                    userDTO.getPassword(),
                    userDTO.getAuthorities(),
                    userDTO.getNickname()
            );
        } else {
            throw new UsernameNotFoundException("사용자명을 찾을 수 없습니다.");
        }
    }

위 코드는 int authoritiesSize = userEntity.getAuthorities().size(); 이라는 임의로 프록시를 사용해서 데이터를 조회해야하게끔 만들게해서 지연로딩을 회피시켰다. 근데 authoritiesSize 라는 쓸모없는 코드가 있으면 보기 좀 그러니까 다른 방법이 있다.

Hibernate.initialize() 사용

 @Transactional
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    		...
            UserEntity userEntity = userEntityOptional.get();
			Hibernate.initialize(userEntity.getAuthorities());

위에 코드대신에 Hibernate.initialize()으로 authorities 컬렉션이 초기화되고 실제 데이터베이스 조회가 발생된다. 물론 hibernate를 사용해야한다


후기

JPA는 늘 JPARepository를 extends만 했지 제대로 이해하고 사용한건 드물었다. 이번기회로 영속성컨텍스트와 lazy loading에대해서 알게되어서 진짜진짜 보람찬 오류해결이었다..! 맛있다..!!!! 이 내용을 토대로 지연로딩을 회피하는방법에대한 글을 하나 써서 기억해둬야겠다~

profile
후회하지 않는 사람이 되자 🔥

0개의 댓글