현재 프로젝트 두 개를 동시에 진행하고 있다.
두 프로젝트 모두 스프링을 사용하여 백엔드 개발을 하고 있지만, 하나는 thymeleaf 템플릿을 사용하여 서버 사이드 렌더링 개발을 하고 있고, 다른 하나는 spring으로 rest api 서버를 개발하고 있다. 둘 다 스프링을 사용하지만, 개발하는 방법이 달라 많이 헷갈리긴 하지만 뭔가 많은 것을 한번에 배우고 있는 것 같아 너무 뿌듯하고 재미있게 개발을 하는 중이다.
오늘 두 프로젝트를 동시에 개발하면서, 팀원들과 나누었던 이슈 상황과 개발하던 중 흥미롭게 고민했던 내용들을 기록해보려고 한다.!
- DTO가 있는 굳이 VO를 사용하는 이유는 ?
쏘마에 다니는 팀원이 멘토링을 받다가 "입력 폼으로 전달되는 값들은 대부분 불변인 값들이기 때문에 이러한 경우 자기의 회사에서는 VO를 사용하여 DTO와 분리하여 관리"한다는 멘토님의 이야기를 듣고 같이 고민하는 시간을 가졌다. 나는 지금까지 DTO만 사용해보고 VO는 사용해 본적이 없다. 먼저 VO로 관리하게 되면 아무튼 DTO로 다시 매핑을 시켜주며 다른 계층으로 전달해주어야 하는 번거로움이 있을거 같은데 왜 사용하는지 의문이 들었다. 우리의 결론은 실제 많은 개발자가 협업하는 실무에서는 불변으로 다루는 값들에 대해서 조심히 관리하기 위해 VO를 사용하는 것이라고 결론을 내렸다. 그렇지 않으면, 실수로 다른 사람이 그 값을 변경할 수 있기 때문에? 아닐 수도 있겠지만.
- @ManyToMany를 연관 객체 테이블을 통해 @ManyToOne과 @OneToMany로 분리하였을 때, 연관 객체 테이블에 데이터를 저장하는 이슈
실제 request로 전달받은 Dto로 받아온 객체를 JPA를 통해 저장하려고 하는 실수로 인해 발생한 이슈였다. 실제 연관 관계로 되어있는 필드를 저장하기 위해서는 데이터베이스를 통해 객체를 가져오거나 영속성 컨텍스트에 있는 객체를 사용하여 저장해야 점을 잊고 있었다.. 그래서 @Transactional도 걸어보고, 메서드도 분리해보고, 컴퓨터도 꺼보고 해봤지만, 해결하지 못한 이슈를 갑자기 어느 순간 깨달아 해결한 이슈였다. 해결했을 때 성취감이 대단했지만, 바보같은 짓만 반복해서 웃기기도 했다.
- @ManyToOne 연관관계에서 양방향 연관관계를 맺어주기 위해 @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER) 와 같이 작성한 팀원이 데이터가 삭제가 안되는 이슈 발생!
이 부분은 비전공자인 팀원이 나한테 물어보았던 이슈였다. 컨트롤러, 서비스, 레포지토리 모두 로직이 이상한 점이 없었으며, 실제 값이 잘 전달되는지도 로그를 통해 확인하였다. 원인은 엔티티에 있었던 것이다. 양방향 연관관계를 걸어주기 위해 mappedBy, cascade 속성은 잘 걸어주었지만 fetch 속성이 문제가 되었다. 사실 EAGER를 잘 사용하지 않기 때문에 default가 LAZY인 @OneToMany를 EAGER로 굳이 변경해준 것에 대해서는 의아했지만, 이것이 해당 이슈의 원인이라는 것은 아직도 모르겠다.. 좀 더 구글링을 해봐야겠다.
- @ModelAttribute를 생략하면 안되는 이유?
@GetMapping("/xx")
public String XXX(FormDto formDto) {
// 생략
return "xxx_form";
}
이것 또한 팀원이 나에게 물어본 질문이었다. Model을 사용하지 않았는데 객체를 매개변수를 통해 thyemleaf에 객체를 전달할 수 있냐는 질문에서 시작되었다. 나는 그 객체 앞에 @ModelAttribute가 생략되었다는 것을 알려주었으며, 추가로 @ModelAttribute를 생략하는 이유? 아니면 어느 경우 생략을 해야하는지 추가로 나에게 물어보았다. 나는 실제 @ModelAttribute를 생략하지 않는다. @RequestaParam도 생략하지 않는다. 왜냐하면 @RequestParam도 생략 가능하기 때문에 두 어노테이션이 동시에 사용되는 메서드에서 이 둘 모두 생략한다면 나도 헷갈릴 수 있고, 같이 개발하는 팀원들에게도 혼란을 줄 수 있다고 생각하기 때문에 두 어노테이션 모두 생략할 수 있지만 저는 생략하지 않는다고 말햇다.!
- 리소스를 식별하는 일련변호를 URL에 포함시켜 식별할 것인가? Cookie에 저장하여 Session과 함께 관리하여 식별할 것인가?
이 부분은 내가 개발을 하면서 궁금증이 생겨 강사님께 질문한 내용이다. 어떠한 리소스를 식별하기 위해서 아무도 예측할 수 있는 uuid값을 생성하여 서버가 요청이 올 때마다 그 값을 식별할 수 있는 방법은 URL에 저장하여 PathVariable로 값을 가져오거나 Cookie에 저장하여 매 요청 마다 쿠키값을 통해 값을 가져올 수 있다고 생각했다. 결론은 URL에 저장하여 식별하는 방법을 사용해야한다. 왜냐! 먼저 리소소를 식별하는 값은 대부분 URL에 표현을 한다. 생각해보면 당연하다 URL은 리소스의 위치를 알려주는 주소이기 때문이다. 그리고 URL에 저장한다면 @PathVariable를 통해 쉽게 가져와 개발할 수 있다. 만약 쿠키를 사용한다면 쿠키값을 가져오기 위해 @CookieValue와 세션값과 비교를 위해 request.getSession(true)을 통해 세션을 가져오고 다시 세션에서 쿠키값을 통해 리소스의 정보를 가져와야 하기 때문에 매우 복잡한 로직이 필요했다.
// 생략
public String cookieTest(@CookieValue String storeId, HttpServletRequest request) {
HttpSession session = request.getSession(true);
Object attribute = session.getAttribute(storeId);
// 생략
}
질문하기 전에 URL에 리소스 식별 번호(UUID) 값을 넣는다면 너무 URL가 안이쁠까봐 Cookie 이용하여 개발했는데, 다시 고쳐야한다..ㅠㅠ 생각해보면 왠만한 사이트의 URL의 리소스 식별값은 너무 길고 안이뻣다..! 아래 캡처본이 현재 벨로그 작성중인 URL이다.

- Rest Api 서버를 개발할 때, 예외 발생시 해당 예외를 잡아 응답 API를 만드는 처리
컨트롤러 쪽에서는 해당 요청에 대해서 성공시, 응답 바디에 담을 객체를 미리 반환값으로 지정하여 반환한다. @RestController를 통해 자동으로 json 형태로 응답 메시지 바디에 담아 전달한다. 하지만 만약 예외가 발생하였을 경우는 성공했을 경우와 동일하게 메시지 바디를 생성할 수 있다. 왜냐 그 형태를 맞춰서 만들 수 없기 때문이다. 단순히 데이터를 보내는 것이 아닌 너의 요청이 실패했고 뭐 때문에 실패했다는 정보만 전달하고 싶을 것이다. 이를 해결하기 위해 Spring은 ControllerAdvice 기능이 존재한다.!
@RestController
@ControllerAdvice
public class ExceptionAdvice {
// 생략
@ExceptionHandler(LoginFailedException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
protected CommonResult loginFailedException() {
return responseService.getCommonResult(404, "Login Failed");
}
// 생략
}
실제 예외를 기존의 예외를 사용하지않고 해당 이슈에 대한 예외를 RuntimeException을 상속받아 만들어주고 다음과 같이 사용하면, 해당 예외 발생시 자동으로 응답코드와 해당 메시지만을 응답 메시지 바디에 담아 응답할 수 있다. 자세한 문법은 ControllerAdvice에 대해서 찾아보세요~