코딩하는 털보

21.11.08 TIL 본문

Diary/Today I Learned

21.11.08 TIL

이정인 2021. 11. 9. 00:46

오늘의 삽질

@AuthenticationPrincipal에서 받아온 User 객체를 EntityManager에서 사용하기.

Habit

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    @JsonIgnore
    private User user;

User

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private List<Habit> habit;

Habit과 User는 ManyToOne 연관관계를 가지고 있다. (연관관계의 주인은 Habit)
문제는 새로운 Habit을 생성하는 구조에서 사용되는 아래의 메서드이다.

    protected void setUser(User user) {
        this.user = user;
        user.getHabit().add(this);
    }

위 메서드에 아규먼트로 입력되는 User는 컨트롤러 레이어에서 @AuthenticationPrincipal로 받은 사용자 객체이다.
즉, UserDetailsService를 구현한 서비스 클래스에서 DB의 User 테이블에서 가져오는 User인 것이다.

    public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException {
        User user = userRepository.findBySocialId(userId)
                .orElseThrow(() -> new UsernameNotFoundException(userId + "를 찾을 수 없습니다."));

        return new UserDetailsImpl(user);
    }

User를 DB에서 조회해 가져왔으므로 당연히 문제없이 user.getHabit()이 문제없이 동작할 것으로 예상했으나,
LazyInitializationException 에러가 발생하면서 작업이 종료된다.

원인을 찾아 본 결과, @AuthenticationPrincipal로 받은 user가 준영속 상태인 것.
그 이유는 UserDetailsService에서 User를 받아오는 부분은 JwtAuthenticationFilter인데,
이 Security Filter들은 EntityManager의 도움을 받을 수 없는 영역이기 때문이다.

첫번째 해결책은 서비스 레이어에서 User를 DB로부터 다시 꺼내오는 것이다.
하지만 어색함이 없지않다. 분명히 UserRepository에서 받아왔는데 실제로 사용하기 위해서 한번더 DB를 조회해야한다니..

우리가 선택한 두번째 해결책은 EntityManager의 가용 영역을 넓히기 위해서 OpenEntityManagerInViewFilter를 등록하는 것이다.

@Configuration
public class OpenEntityManagerConfig {
    @Bean
    public FilterRegistrationBean<OpenEntityManagerInViewFilter> openEntityManagerInViewFilter() {
        FilterRegistrationBean<OpenEntityManagerInViewFilter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
        filterFilterRegistrationBean.setFilter(new OpenEntityManagerInViewFilter());
        filterFilterRegistrationBean.setOrder(Integer.MIN_VALUE);
        return filterFilterRegistrationBean;
    }
}
  • OpenEntityManagerInView : JPA를 지원하기 위해 사용되며 EntityManager가 thread 전체에서 적용되도록 한다.

위의 Config 파일을 사용하면 @AuthenticationPrincipal로 받아온 User를 영속 상태로 EntityManager가 관리할 수 있고
서비스 레이어에서 원하는 방식으로 사용할 수 있다.

Comments