인프런 커뮤니티 질문&답변

스물다섯컷의흑백필름님의 프로필 이미지

작성한 질문수

자바 ORM 표준 JPA 프로그래밍 - 기본편

프록시

영속성 컨텍스트 프록시 오류 질문

24.06.25 20:05 작성

·

146

0

[질문 템플릿]
1. 강의 내용과 관련된 질문인가요? (예/아니오)
2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)
3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)

컨트롤러

@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@PreAuthorize("isAuthenticated()")
public ResponseEntity<Long> createWalkInvitation(
        @RequestPart("walkInvitaionCreateRequestDto") WalkInvitaionCreateRequestDto request,
        @RequestPart("uploadPhotos") MultipartFile[] multipartFiles,
        @AuthenticationPrincipal PrincipalDetails principalDetails){

    Long savedId = walkInvitationService.createWalkInvitation(request, multipartFiles, principalDetails);

    return ResponseEntity.ok().body(savedId);
}

서비스 계층

 

public Long createWalkInvitation(WalkInvitaionCreateRequestDto request,
    MultipartFile[] multipartFiles, PrincipalDetails principalDetails) {

    Member member = principalDetails.getMember();

    WalkInvitation walkInvitation = WalkInvitation.builder()
        .writer(member)
        .title(request.getTitle())
        .content(request.getContent())
        .latitude(request.getLatitude())
        .longitude(request.getLongitude())
        .detailedLocation(request.getDetailedLocation())
        .walkStartDateTime(request.getWalkStartDateTime())
        .walkingStatus("산책 대기 중")
        .build();

    // member.getWalkInvitations().add(walkInvitation);

    List<Photo> photos = photoService.savePhotosToWalkInvitation(multipartFiles, walkInvitation);
    walkInvitation.addPhotos(photos);

    walkInvitationRepository.save(walkInvitation);

    return walkInvitation.getId();

위 주석처리 한 부분// member.getWalkInvitations().add(walkInvitation)에서 failed to lazily initialize a collection of role가 발생합니다.

오류가 발생하는 이유는 JWT 토큰 방식을 구현한 스프핑 시큐리티의 인증/인가 과정에서 해당 컨트롤러에 도달하기전에 필터에서 이미 SecurityContext에 principalDetails를 authentication 하면서 principalDetails라는 객체에 member가 저장되어 있는 상태입니다.

그래서 이미 서비스 계층에서 위 코드에 Member member = principalDetails.getMember();를 호출하는 시점에는 getMember 호출 시 DB에 접근해서 값을 가져오는 것이 아닌, 순수한 객체 상태의 member를 가져오기 때문에 영속성 컨텍스트에 member가 저장되어 있지 않기에 프록시 오류가 발생하는 것 같습니다(로그로 DB에 접근하지 않는 것 확인)

 

이러한 문제를 해결하기 위해서는 principalDetails.getMember()로 얻은 멤버정보로 DB에 저장된 Member를 다시 가져와서 영속성 컨텍스트에 저장시키는 방식을 택해야 할까요?

스프핑 시큐리티의 인증/인가 과정(JwtAuthorizationFilter)에서 accesstoken을 바탕으로 이미 member를 db에서 찾아오는 쿼리문이 한번 나갔는데, 또 호출하려니 조금 비효율적인 것 같아서 고민이 됩니다. 다른 방법이 있을까요?



답변 2

0

y2gcoder님의 프로필 이미지

2024. 06. 26. 11:36

안녕하세요. 이민혁님, 공식 서포터즈 y2gcoder입니다.

넵 이럴 때는 보통 id를 이용해서 Member를 다시 가져와서 영속화 상태로 만들어주셔야 할 것 같습니다!

이러한 부분과 더불어 실무에서는 인증 객체에 엔티티 자체를 저장하는 것보단 인증에서 사용할 DTO를 저장하는 편인 것 같습니다!

감사합니다.

 

  • AI 인턴 답변 링크의 영한님 답변도 참고해주십쇼!

0

인프런 AI 인턴님의 프로필 이미지

2024. 06. 25. 20:11

안녕하세요, 인프런 AI 인턴입니다.

이민혁님의 질문은 영속성 컨텍스트와 관련된 프록시 오류에 대한 것으로, 특히 JWT 인증/인가 과정에서 미리 인증된 객체를 사용함으로써 발생하는 지연 로딩(lazy loading) 문제에 관련된 내용입니다. 주요 문제는 서비스 계층에서 principalDetails.getMember()를 호출할 때, 이미 인증된 컨텍스트에서 가져온 순수한 객체 상태의 member를 사용하기 때문에 DB로부터 member를 실제 로딩하지 않고, 영속성 컨텍스트에 저장되지 않은 상태에서 컬렉션을 초기화하려고 할 때 ‘failed to lazily initialize a collection of role’ 오류가 발생합니다.

이 상황에서, 이미 JWT 인증/인가 과정을 통해 DB에서 member 정보를 얻어왔음에도 불구하고, 서비스 계층에서 다시 DB로부터 member 정보를 조회해야 하는지에 대한 고민이 있습니다. 정확히 말씀드리면, 이런 상황에서는 영속성 컨텍스트 내에 해당 엔티티(member)를 다시 로딩해야 하는 경우가 발생합니다. 이는 프록시 초기화 문제를 해결하기 위한 일반적인 접근 방법 중 하나입니다.

질문과 매우 유사한 상황을 다루는 예시가 있어 해당 답변을 참고하시는 것도 도움이 될 것입니다:

  • 이러한 문제의 한 해결 방법으로, Transaction 범위 내에서 member 엔티티를 다시 조회하여 영속성 컨텍스트 내에 로드하는 방법을 검토할 수 있습니다. 쉽게 말해, 서비스 메서드 내에서 principalDetails.getMember().getId()와 같이 ID를 얻어와 해당 ID로 member를 다시 조회할 수 있습니다. 이렇게 하면, 해당 member 엔티티에 대해 연관된 컬렉션에 접근할 때 지연 로딩이 정상적으로 수행될 수 있습니다.

이와 관련하여 유사한 상황과 해결책을 제시하는 기존 답변 링크는 다음과 같습니다:

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.