[백엔드] 스프링 부트와 JPA 활용 4 - 웹 계층 개발

유승선 ·2022년 8월 16일
0

오랜만에 올리는 백엔드 관련 글이다, 드디어 웹 계층으로 넘어가기 시작했고 이번편에서는 화면에서 보여지는 view 에 초점을 두었기 때문에 bootstrap 또한 사용하였고 타임리프를 활용한 html 파일 제작에도 경험치를 쌓았다.

솔직히 타임리프 관련해서 사용하는 거는 하나도 못 알아듣겠고 많이 헷갈리지만 일단 백엔드에 집중을 하고 강의를 들었다.

홈 화면과 레이아웃

홈 화면과 레이아웃

예전에 스프링 가장 기본편에서 들었던 RequestMapping 어노테이션이 나왔고 예전에 설명해줬을때와 같이 스프링이 저 어노테이션을 보자마자 home.html 을 부를것을 생각하고 있었다.

home.html 은 보이다 시피 resources 밑에 있는 templates 에 넣어주었다. 그리고 타임리프에서 보이는 fragments/ 라는 코드 때문에 (include 라고 생각하라고 했다) 그 안에 디렉토리에 fragments 파일을 한번 더 만들어 주었다.

당장 저렇게 만들면 홈 화면이 살짝 바뀌는데 이쁘게 바뀐건 확실히 아니였고 예전에 한번 사용해봤던 Bootstrap 을 적용해봤다.

부트스트랩 이쪽 링크에 들어가서 Download 를 한뒤에 css, js 파일을 아래에 resources -> static 파일 밑에다가 넣어주었다.

그리고 높은 확률로 복붙을 했기때문에 바로 적용이 안될수도 있었기에 돌아가고 있던 서버를 잠시 꺼주었고,

저기 Reload from Disk 를 한번 눌러주고 Build 에 들어가서 Build Project 를 넣어 준 후에 한번 새로고침 했더니 화면이 썸네일에 나오는것처럼 이뻐졌다.

추가
Control + E 버튼을 누르면 이 전에 사용했던 파일 이름이 나오는데 상당히 편리하다

회원 등록

회원 등록

GetMapping 과 PostMapping 어노테이션을 사용했고 타임리프를 이용한 폼 submit 과 오브젝트를 이용한 타임리프 활용법에 대한 내용이었다.

앞서 만든 회원가입 버튼을 누르게 되면은 /members/new 로 가는것을 확인 할 수 있다.

멤버 폼을 오브젝트로 만들기 위해 따로 클래스를 만들어 주었다. 그리고 name,city 등등 필요한 정보를 적게 됐는데.

@NotEmpty 라는 어노테이션이 눈에 띈다. 이 부분은 이름 부분을 필수로 작성해야 한다라는 Validation 을 넣어준거라고 생각하면 된다.

오랜만에 보는 Model 클래스를 넣어주는 폼이다. 모델 클래스를 저렇게 넣어주면은 "memberForm" 이라는 attributeName 을 활용해서 타임리프를 사용한다라고 생각하면 된다. createForm 을 누른 순간 member/createMemberForm 을 부르며 리다이렉트 된다.

타임리프를 이용한 HTML 파일이다. 굉장히 복잡하지만 잘 보면은 form 안에 method 가 post 로 설정되있고 위에서 적었던 @PostMapping 을 적은 함수에 타임리프 정보가 그쪽으로 넘어가게 된다.

타임리프에 대한 공부가 훨씬 많이 필요하겠지만 th:: 라고 적혀있는 부분은 타임리프 코드를 사용한다고 생각하면 된다. 지금 memberForm 이라는 attributeName 을 활용하며 name 과 여러가지 필드를 사용하는것을 확인 할 수 있다.

PostMapping 어노테이션에 대한 추가설명인데, MemberForm 앞에 @Valid 어노테이션은 MemberForm 에 회원 이름이 꼭 들어갈 수 있도록 설정을 해놨고 저 어노테이션을 쓰면은 Validation 을 하는 오브젝트라고 알려주는거라고 생각하면 된다.

그 외에는 타임리프로서 action 폼으로 정보들이 들어 왔으므로 멤버 오브젝트에서 get 을 써주면 된다. BindingResult 같은 경우는 혹여나 에러가 있을경우 createMemberForm 으로 리디렉트 보내주는 역활을 수행하고 있다.

타임리프에서 참 신기하다고 생각했는데 if fields 에러가 있을경우 에러메세지를 출력하고 있다. 실제로 폼 데이터에서 에러가 있을경우 에러 메세지를 출력한다.

추가적인 설명은 타임리프 웹사이트를 참고해서 Spring 섹션을 간 후에 검색을 하면은 될거같다.

추가

PostMapping 에서 멤버 엔티티를 안넣고 MemberForm 을 넣어준 이유는 필요한 정보만을 넣고 싶은것도 있고 기존에 Controller 클래스에 있는 코드를 더 깔끔하기 위해서다

회원 목록 조회

회원 목록 조회

추가로 더 설명할 만큼 새로운 내용은 등장하지 않았고, 회원 목록을 조회하는 것이기 때문에 findMembers() 함수를 써주었다.

위에 함수에서 Model 객체를 memberList.html 로 넘겼고 타임리프에서 th:each 라는 함수를 쓰면서 리스트를 읽어주고 알맞게 출력 해주었다.

상품 등록

상품 등록

회원 등록과 굉장히 유사하게 해주었고 조금 특별하게 주시할 점은 MemberForm 을 만든것처럼 BookForm 도 따로 만들어주었다.

그리고 PostMapping 에 보면은 setter 을 많이 쓴 모습인데, 예제가 간단해서 그렇지 원래는 Order 에서 만든것처럼 생성자로 한번에 전부 넣어줄수 있게 만드는게 훨씬 더 깔끔하고 좋다고 강조했다.

createItemForm.html 도 만들어주었다.

상품 목록

상품 목록

회원 목록 조회와 굉장히 유사했다.

타임리프 문법에 좀 더 익숙해지고 싶다.

상품 수정

상품 수정

아이템을 수정하는 코드를 작성해봤다. 다른 조회보다 조금 더 복잡한 로직을 가지고 있기 때문에 좀 더 집중을 해봤다.

이 코드를 내가 잘 이해를 못했는데, 각 아이템마다 고유의 숫자가 다르기 때문에 GetMapping 에 URL 에서도 {itemId} 라는 변수가 추가 되었다.

그리고 Edit 을 누르게 되면 원래 설정된 정보가 그대로 출력이 되야하기 때문에 BookForm 을 새로 만들어서 itemId 를 기반으로 기존에 책을 찾은 후에 정보를 그대로 입력했다.

또한번 타임리프를 이용해서 정보를 수정하고 업데이트된 객채를 submit 해서 넘겼다.

포스트 매핑을 보면은 BookForm 을 받은것을 확인 할 수 있는데 Book 객채를 만들어서 정보를 업데이트 하고 itemService에 새로운 객체를 업데이트 시켜줬다.

아이템 리포지토리에 save 를 보면을 알 수 있지만, 지금 bookId 가 NULL 이 아니기 때문에 merge() 를 불러서 업데이트 한것을 확인 할 수 있다.

변경 감지와 병합 (merge)

변경 감지와 병합 (merge)

변경 감지와 병합은 정말 중요한 내용이라고 강조 했고 집중을 해서 들어보았다.

변경 감지란?

JPA 에선 기본적으로 변경 감지라는것을 수행하고 있는데 만약 JPA 에서 관리하는 객체에 어떤 변화가 일어난다면, (setName() 등등) 자동으로 update 쿼리를 날려주고 이를 변경 감지를 하고 있다고 한다.

준영속 엔티티란?

  • 영속성 컨텍스트가 더는 관리하지 않는 엔티티이다

  • 여기 나와있는 것처럼 현재 수정을 시도하는 Book 은 이미 DB에 한번 저장이 되어있었던 엔티티고 기존 식별자를 가지고 있다. 즉, JPA 가 관리를 하지 않고 있는 객체이다.

  • 만약에 itemService 객체에서 saveItem() 을 쓰지 않았다면 업데이트 쿼리는 절대 안날라 갔을거다.

준영속 엔티티를 수정하는 2가지 방법

  1. 변경 감지 기능 사용
  2. 병합 사용 (merge)

변경 감지 같은 경우는 위에서 설명한것과 동일하게 findItem 으로 기존 객채를 DB에서 찾아서 오고 itemParam 으로 form 에서 업데이트된 객채를 가져온 뒤에 setPrice() 해주는것으로 JPA 에서 자동으로 update 쿼리가 나갈수 있게하는것이다.

병합(merge) 사용

병합의 원리는 아래와 같다.

그러나 병합(merge) 를 사용하는 방법은 그렇게 추천하는 방법은 아니다.

만약에 merge 를 사용하게 되고 수정하는 단계에서 특정 필드만 수정하게 된다면은... 수정하지 않은 필드는 null 로 남게 되기때문에 update 쿼리가 실행되면서 DB에 데이터가 null 로 변경이 된다.

현재 save() 메서드에서는 merge() 를 사용하고 있고 로직 자체가 간단하기 때문에 사용을 해도 별 문제가 없지만 좀 더 까다로운 수정 조건이 있다면은 정말 안좋은 방법이다.

가장 좋은 해결 방법

이렇기 때문에 변경감지를 사용하며 준영속 상태의 엔티티를 수정할때

컨트롤러에서 어설프게 엔티티를 생성하지 말라는 뜻은

이런 식으로 준영속 엔티티를 만들지 말라는것이다. setter 을 너무 남발하는것도 그렇고 saveItem 함수 자체가 merge 를 사용하고 있기때문에 수정 사항에 변경이 생기면 참 안좋은 방법이다.

그렇기 떄문에 수정된 방법으로 updateItem 함수를 따로 만들어 주어서 필요한 정보만 입력을 한뒤에

이런식으로 변경감지를 이용해 기존에 DB 데이터를 가지고 오고 그대로 setter() 을 사용해주며 JPA 가 자동으로 update 쿼리를 날릴 수 있게 하는것이다.

물론! setter() 을 계속 사용하고 있고 이 또한 그렇게 좋은 방법은 아니라고 강조 하였다.

상품 주문

상품 주문

웹 계층 개발 부분에서는 항상 어떤 부분의 Controller 을 만들어 주었고 이번에도 OrderController 클라스를 따로 만들어서 주문 관련된 모든것을 관리해주었다.

위에 @GetMapping 부분은 상품 주문 폼을 만들어 주었고 밑에 있는 order 클래스는 타임리프로 작성한 정보에 맞춰서 주문을 실행 해주고 있다.

오더 폼에 있는 타임리프는 위와 같이 작동하고 있다. method = "post" 로 되있는것을 확인 할 수 있고 "members" 모델을 읽으면서 value로 멤버 아이디를 포스트에 전송하는것을 볼 수 있다.

주문 목록 검색, 취소

주문 목록 검색, 취소

주문 목록을 검색하고 취소 하는 코드를 만들었다. orderList 를 렌더링 하는 과정은 model 을 통해서 오더 리스트를 받은 후에 넘기고 이전 모든 과정들과 동일하다.

타임리프 렌더링에서는 모델 값을 받아서 읽는것을 확인 할 수 있다.

사실 가장 어려웠던 부분은 cancel 하는 동작 과정인거 같은데 정말 마법같게도 캔슬을 누르고 PostMapping 으로 넘어갔다.

여기로 넘어온 후에는 이 전에 만들었던 cancelOrder 함수를 통해서 자연스럽게 주문을 취소 해줬다.

배운점

배운점

꽤 길었던 강의라 생각했고, 좀 꾸준히 하고 있었는데 아쉽게도 중간에 큰 공백이 생겨서 이제야 강의를 끝냈다. 전반적인 스프링에 대한 이해도와 자바의 이해도가 높아지고 있는걸 느끼고 있고 이번 강의에서 제일 헷갈렸던것은 타임리프 문법이었던거 같다. 공부하기 힘들어 보이지만 다음 강의에서 API 에 대한 이해를 높이고 나면은 본격적으로 타임리프에 대한 공부 또한 해보고 싶다.

profile
성장하는 사람

0개의 댓글