controller 리턴타입을 엔티티로 했을 때 생기는 일
no Session, ByteBuddyInterceptor, 무한루프... DTO 를 쓰러 가야겠다는 생각뿐.
Sep 05, 2024
컨트롤러에
@ResponseBody
를 붙여 리턴하게 되면 Json
데이터 형식으로 값을 리턴한다.값을 전달할 때 사용되는 객체는
DTO
인데왜 엔티티클래스를 사용하지 않는것일까?
(애초에 엔티티는 데이터 전달에 사용되는 객체가 아니지만...)
엔티티클래스를 컨트롤러에서 리턴하면 생기는 일을 알아보자.
1. spring.jpa.open-in-view
=
false
일때
간단히 설명하자면 JPA 가 데이터베이스 커넥션을 언제 반납할 지 결정하는 설정이다.
public class Board {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Integer id;
@Column(nullable = false)
private String title;
@Column(nullable = false)
private String content;
@CreationTimestamp
private Timestamp createdAt;
@ManyToOne(fetch = FetchType.LAZY)
private User user;
@OneToMany(mappedBy = "board")//반대 방향에도 거는게 양방향 매핑
private List<Reply> replies;
@Builder
public Board(Integer id, String title, String content, Timestamp createdAt, User user) {
this.id = id;
this.title = title;
this.content = content;
this.createdAt = createdAt;
this.user = user;
}
}
Board 엔티티가 이렇게 존재하고
@GetMapping("/v1/board/{id}/update-form")
public @ResponseBody Board updateForm1(@PathVariable("id") int id, HttpServletRequest request) {
User sessionUser = (User) session.getAttribute("sessionUser");
Board board = boardService.게시글수정화면(id, sessionUser);
return board;
컨트롤러의 리턴을 엔티티로 설정한다.
이렇게 하면

저 부분에서 에러가 발생하게 된다.

opne-in-view
를 false 로 설정해 놓았기 때문인데이미
게시글수정화면
서비스에서 데이터를 다 조회했는데 어째서 영속성 컨텍스트나, opne-in-view 나 session 이 연관이 되어있냐 하면- 게시글 수정화면에서
findById
를 실행한다 (user는 LAZY 전략으로 인해 가져오지 않음)
- 컨트롤러가 json 형식으로 데이터를 변환한다.(
ResponseBody
때문에)
- private User user; 차례가 되면 컨트롤러는
null
값을 마주하게 되고, 하이버네이트는 get 요청을 한 줄 알고 값을 찾으러간다.
- 그러나 이미 데이터베이스 커넥션은 반납되었기 때문에
session
을 찾을 수 없다.
그럼 데이터베이스 커넥션을 true 로 바꿔서 연결하게 해주자.
2. ByteBuddyInterceptor
현재 상황. opne-in-view 를 true 로 변경한다. → controller 에서도 db에 접근이 가능해짐.

ExceptionHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor]]
분명 controller 에서도 접근이 가능하도록 설정을 변경 했는데도 에러가 뜬다.
ByteBuddyInterceptor 는 LAZY 로딩과 관련이 있다.
엔티티를 Json 데이터로 만드는 도중 아까처럼
User
을 만나게되면 값을 조회하러 db 로 달려가지만, 이미 비어있는 객체를 Serialize 하기 때문이다.무슨 설정 방법이 있다는데 그냥 DTO 를 쓰는게 제일 간단하다.
3. 무한 루프
지금 양방향 매핑된 엔티티가 있다.


- 컨트롤러
@GetMapping("/v3/board/{id}")
public @ResponseBody Board detailV3(@PathVariable("id") Integer id) {
User sessionUser = (User) session.getAttribute("sessionUser");
Board model = boardService.게시글상세보기V3(sessionUser, id);
return model;
}
- 서비스
public Board 게시글상세보기V3(User sessionUser, Integer boardId){
Board boardPS = boardRepository.mFindByIdWithReply(boardId)
.orElseThrow(() -> new Exception404("게시글이 없습니다."));
return boardPS;
}
- 레포지토리
보드와 유저이름, 댓글을 조인하는 쿼리이다.
@Query("select b from Board b join fetch b.user left join fetch b.replies r left join fetch r.user where b.id=:id")
Optional<Board> mFindByIdWithReply(@Param("id")int id);
이 코드를 사용하여 엔티티 클래스에서 json 변환을 하면 이렇게 된다.

큰 네모는 괜찮지만
replies
부터 비극이 시작된다. 양방향 매핑으로 인해replies → board → replies → board → … 서로를 호출하기 시작한다. 영원히.
이때 사용하는 것이
@JsonIgnoreProperties
어노테이션이다 Json 으로 변환할 때 제외할 것을 적는 것인데board 엔티티의
replies
에 추가를 한 뒤 다시 실행해보자.@JsonIgnoreProperties({"board"}) // json 데이터로 만들러 가지마라
@OneToMany(mappedBy = "board")//반대 방향에도 거는게 양방향 매핑
private List<Reply> replies;
{
"id": 5,
"title": "제목5",
"content": "내용5",
"createdAt": "2024-09-05T05:32:27.013+00:00",
"user": {
"id": 2,
"username": "cos",
"password": "1234",
"email": "cos@nate.com",
"createdAt": "2024-09-05T05:32:27.011+00:00"
},
"replies": [
{
"id": 1,
"comment": "댓글1",
"user": {
"id": 1,
"username": "ssar",
"password": "1234",
"email": "ssar@nate.com",
"createdAt": "2024-09-05T05:32:27.010+00:00"
},
"createdAt": "2024-09-05T05:32:27.013+00:00"
},
{
"id": 2,
"comment": "댓글2",
"user": {
"id": 1,
"username": "ssar",
"password": "1234",
"email": "ssar@nate.com",
"createdAt": "2024-09-05T05:32:27.010+00:00"
},
"createdAt": "2024-09-05T05:32:27.013+00:00"
},
{
"id": 3,
"comment": "댓글3",
"user": {
"id": 2,
"username": "cos",
"password": "1234",
"email": "cos@nate.com",
"createdAt": "2024-09-05T05:32:27.011+00:00"
},
"createdAt": "2024-09-05T05:32:27.013+00:00"
}
]
}
replies
가 board
를 호출하지 않아 무사히 데이터가 출력되었다.하지만 굳이 게시글 상세보기를 위해 글쓴이와 댓글작성자의 id, password, email… 모든걸 가져올 필요는 없다.
따라서 DTO를 만들어서 해결하는 것이 좋다.
다음 포스팅에선(조회된) 데이터 관리를 위한 DTO 를 활용하는 방법을 알아보자.
Share article