728x90
문제
org.springframework.messaging.converter.MessageConversionException: Could not write JSON: could not initialize proxy \[com.onlyu.domain.entity.Member#1\] - no Session (through reference chain: com.onlyu.domain.entity.Chat\["chatRoom"\]->com.onlyu.domain.entity.ChatRoom\["member1"\]->com.onlyu.domain.entity.Member$HibernateProxy$XbE9wKRQ\["email"\]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: could not initialize proxy \[com.onlyu.domain.entity.Member#1\] - no Session (through reference chain: com.onlyu.domain.entity.Chat\["chatRoom"\]->com.onlyu.domain.entity.ChatRoom\["member1"\]->com.onlyu.domain.entity.Member$HibernateProxy$XbE9wKRQ\["email"\]) at org.springframework.messaging.converter.MappingJackson2MessageConverter.convertToInternal(MappingJackson2MessageConverter.java:274) ~\[spring-messaging-5.3.26.jar:5.3.26\]
Chat을 구현하게 되면서 Message 전송할 때, 다음과 같은 에러가 발생하였다.
chatRoomNo을 사용하여, ChatRoom에 대한 정보를 받아오는 상황에서 ChatRomm Entity에 속한 Member정보들의 Proxy가 초기화되지 않아서 발생하는 에러였다.
...
public class ChatRoom {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long roomNo;
@ManyToOne(fetch = FetchType.LAZY)
private Member member1;
@ManyToOne(fetch = FetchType.LAZY)
private Member member2;
private LocalDateTime createdAt;
public static ChatRoom of(Member member1, Member member2) {
return ChatRoom.builder()
.member1(member1)
.member2(member2)
.createdAt(LocalDateTime.now())
.build();
}
}
두개의 필드에서 FetchType을 Lazy로 설정하여 지연 로딩을 하고 있었는데, 이런 경우 Jackson 라이브러리에 연관된 Entity를 로딩하려 시도를 하게 된다. 이때 Session이 이미 종료된다면 Lazy로딩이 불가능하므로 에러가 발생하게 된다.
해결방법 1
가장 쉽게 해결할 수 있는 방법은 FetchType을 Eager로 변경하면 에러는 해결된다.
...
public class ChatRoom {
...
@ManyToOne(fetch = FetchType.Eager)
private Member member1;
@ManyToOne(fetch = FetchType.Eager)
private Member member2;
...
}
해결방법 2
하지만 Eager로 설정하게 된다면 연관된 Entity의 모든 정보를 가져오게 되므로 DB 쿼리의 성능 저하가 발생할 확률이 높아진다. 대량의 데이터, 빈번한 조회가 있을 경우 큰 문제로 번질 수 있는 것이다.
이러한 문제를 해결하기 위해 Jackson 라이브러리에서 @JsonIgnoreProperties 어노테이션을 제공한다. 해당 어노테이션은 JSON 변환 시 연관된 Entity를 무시하고 변환을 진행하게 된다.
더해서 @Transactional 어노테이션을 추가해서 DB 작업이 완료되기 전까지 Hibernate Session을 유지시켜서 에러를 방지할 수 있다.
수정하게 된다면
public class ChatRoom {
...
@ManyToOne(fetch = FetchType.LAZY)
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private Member member1;
@ManyToOne(fetch = FetchType.LAZY)
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private Member member2;
}
public class ChatController {
...
@MessageMapping("/chat/sendMessage")
@Transactional
public void sendMessage(@Payload Chat chat) {
ChatRoom chatRoom = chatRoomService.findRoomById(chat.getChatRoom().getRoomNo());
chat.sendMessage(chat.getMessage(), chatRoom);
chatService.saveChat(chat);
template.convertAndSend("/sub/chat/room/" + chat.getChatRoom().getRoomNo(), chat);
}
}
- @JsonIgnorePropeties를 사용해서 해당 필드들을 해당 필드들은 JSON으로 변환하지 않게 만든다. (프록시 객체 상태라서 Member 객체를 가져오지 않고 프록시 객체만 참조하고 있는 상황에서, JSON으로 변환하면 예외가 발생하기 때문)
- @Transactional을 사용해서 chatService.saveChat(chat)을 하는 순간 ChatRoom과 Member가 모두 로딩되어 있기 때문에, LazyInitializationException를 방지할 수 있다.
반응형