로그인을 구현하는 도중..
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의 동작과정을 잘 모르고있기때문에 이번을 계기로 학습을 해야겠다는 생각이 들었다.
문제의 원인은 이렇다.
자연로딩 -> 데이터가 실제로 사용되는 시점에서 데이터 베이스 조회를 실행
getAuthorities()
를 호출함 -> 여기서 내가 이해하기로는, Collection<GrantedAuthority> authorities = userEntity.getAuthorities()
자체로는 db의 조회가 아닌 할당의 개념임즉, getAuthorities()
를 호출하는 행위를 할 때 지연로딩 설정된 칼럼인 Authorities은 프록시를 반환하니 데이터가 (아직)없다 이거죠~
그렇다면 실제 db를 조회하기위해서는 프록시가 사용될 때 발생하겠지?
@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
라는 쓸모없는 코드가 있으면 보기 좀 그러니까 다른 방법이 있다.
@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에대해서 알게되어서 진짜진짜 보람찬 오류해결이었다..! 맛있다..!!!! 이 내용을 토대로 지연로딩을 회피하는방법에대한 글을 하나 써서 기억해둬야겠다~