당니의 개발자 스토리

회원 목록 조회 본문

스프링/실전! 스프링 부트와 JPA 활용1

회원 목록 조회

clainy 2024. 5. 14. 00:36

회원 목록 조회

이번 시간에는 회원 목록을 이제 가입된 데이터를 조회를 해야 되겠죠.

목록 조회를 해보겠습니다.

자 여기 보면 members 라고 나오죠. 그럼 이제 /members를 만들어야 되겠죠.

list의 model 이라는 객체를 통해서 화면에 데이터를 전달하게 됩니다.

자 그리고 엄청 간단하게 memberService.findMembers 메서드를 사용해서 데이터를 다 끌어옵니다.

그 다음에 model.addAttribute 해가지고 그대로 딱 넣어주면 돼요. "members"가 key고, members가 value 입니다. 이렇게 하면 List가 꺼내지겠죠.

그 다음에 return을 할 건데 "members/memberList"로 return 하겠습니다.

그래서 templates에 있는 members에 memberList.html을 만들었습니다.

다시 간단히 말씀드리면,

memberService.findMembers 하면 JPA에서 JPQL로 짜가지고 그냥 모든 멤버를 다 조회하는 거거든요. List로 나온 members를 가지고 와서 어떻게 합니까?

model에 담아서 이 화면에 넘기는 거죠.

자 그러면 이제 루프를 돌면서 HTML에서 쭉 뿌리면 돼요.

물론 뭐 코드를 이렇게 할 수도 있어요. 지금 제가 한 게 뭐냐면,

refactoring 기능 중에 inline 이라고 있어요. Ctrl + T 하면 리팩토링 기능이 떠요. cmd + option + N 하면 inline이 한 번에 됩니다.

그런데 저는 이 model에 addAttribute 할 때는 약간 명확하게 담기는 객체를 잘 참조할 수 있도록 변수를 받아서 넣는 게 편하더라구요. 근데 이건 사람마다 확실히 달라요.

자 그래서 실행할 View를 한번 쭉 만들어 보겠습니다. 복붙해야겠죠.

자 보면, bodyHeader 이건 아실건데요. 지금 보면 정말 단순하게 테이블로 그냥 쭉 돌리면서 뿌리는데요.

일단 <tbody>에서 <tr 에서 이게 있습니다. 이 타임리프가 공부할 땐 좀 어려워요. 근데 이제 공부하고 나면 참 좋은게 여기 보시면 그냥 HTML tag를 그대로 써요. HTML 태그를 그대로 가져다 쓰는게 되게 큰 장점이거든요.

여기 tr th:each="member : ${members} 라고 해서 방금 한 members 기억나시죠.

이 model에다가 넘긴 이 members 라는 리스트를 넘기면,

{member}가 그냥 바인딩이 돼요.

그래가지고 그냥 이렇게 쭉 찍으시면 돼요. 이렇게 하면, list에 그냥 다 나옵니다.

참고로 타임리프에서 여기 물음표가 있잖아요.

만약에 address가 null 이에요. 그러면 더 이상 city를 안 찍어야 되겠죠. 이게 뭔가 NULL이거나 그러면 '얘를 더 이상 진행 안 해!' 라는 표시로 봐주시면 될 것 같습니다. 뭐 이걸 If 어쩌고 저쩌고 이게 데이터가 없으면, 이게 NULL이면 너무 적기가 번거롭잖아요. 그래서 타임리프가 이러한 물음표 문법을 지원합니다. 이런 거를 좀 옵셔널로 하면 좋을 텐데 좀 아쉽죠.

자 한번 해볼게요.

회원 목록으로 가면 데이터가 없으니까 가입을 한번 해볼게요.

이렇게 해서 submit 한 다음 목록에 가보면,

이제 생겼죠. 이거를

이러한 루프를 돌리면서 타임리프 문법인 th:eac로로 쭉 돌리면서 뿌렸습니다.

이제 회원 가입이랑 목록하면 회원 기능은 끝나거든요. 회원 수정 기능을 안 만들었어요. 수정은 상품 수정에서 보여드릴게요.

참고로 또 강조하고 싶은 게 여기에다가 form 객체를 써야 되냐, 엔티티를 직접 사용해야 되냐에 대한 문제가 항상 걸려 들어와요.

요구사항이 정말 단순할 때는 MemberForm 없이 Member 엔티티를 그대로 써도 돼요. 그렇게 해서 등록이나 수정에서 Member 엔티티를 써도 되는데 요구사항이 실무에서 그렇게 단순하지 않거든요. 일대일로 매칭되는 경우는 거의 없어요.

결국 문제가 뭐냐면 엔티티를 form으로 써버리면 엔티티가 화면을 처리하기 위한 기능이 하다보면 점점 증가돼요. 그러면서 결과적으로 엔티티에 화면 종속기능이 계속 생겨나거든요. 이러면 화면 기능 때문에 엔티티가 지저분해진단 말이에요. 이렇게 되면 결국 유지 보수하기가 어려워져요.

정말 JPA를 쓸 때 조심해야 되는 게 엔티티를 최대한 순수하게 유지를 해야 돼요. 최대한 Dependency 없이 오직 핵심 비즈니스 로직에만 Dependency가 있도록 설계하는 게 진짜 중요해요. 그렇게 해야 애플리케이션이 점점 커져도, 엔티티를 여러군데서 되게 유연하게 사용하더라도 유지 보수성이 높아지거든요.

그래서 아무튼 실무에서는 엔티티에는 핵심 비즈니스 로직만 있고, 화면을 위한 로직은 없어야 돼요. 이제 화면에 맞는 API나 이런 거는 form 객체나 DTO를 사용하셔야 됩니다. 그래서 화면에 맞는 API나 form 객체나 Data Transfer Object라고 DTO가 있죠. DTO는 일반적으로 getter, setter만 있는, 데이터를 전송하기 위한 객체를 말합니다. DTO 사용을 권장드려요.

아무튼 엔티티는 최대한 순수하게 유지를 하셔야 됩니다. 엔티티의 핵심 비지니스 로직 호출하고 했는데 화면 관련된 코드가 있어. 핵심 비즈니스 로직을 손댔더니 화면이 깨지거나, 반대로 화면을 수정하려고 했더니 핵심 비즈니스 로직이 안돌아가면 정말 큰일 나겠죠. 솔직히 화면에서 기능이 안돌아가는 것은 다시 고치면 되잖아요. 그런데 핵심 비즈니스 로직이 틀어져서 돈 안맞고 이러면 큰일 나겠죠.

여기서는 컨트롤러가 등록할 때는 Form 객체를 썼는데,

뿌릴 때는 Member 엔티티를 그대로 출력하고 있죠. 예제가 정말 단순하니까 이렇게 한 거고, 사실은 이것도 Member 엔티티를 그대로 뿌리기보다는, 조금 실무적으로 복잡해지면 이것보다는 DTO로 변환을 해서 화면에 꼭 필요한 데이터들만 가지고 출력하시는 것을 저는 권장을 드립니다.

근데 예제에서는 트레이드 오프를 한 거죠. 화면에 뿌리는데 정말 엔티티를 손댈 게 없는 상황이거든요. 엔티티를 손 안 대고 정말 심플하게 화면을 출력할 수 있는 상황이기 때문에 그냥 이 Member 엔티티를 그대로 반환했습니다.

자 그런데 이것도 뒤에 API 할 때 설명을 드릴 건데, 템플릿 엔진에서 이렇게 렌더링을 할 때는 어차피 서버 안에서 기능이 돌기 때문에 이 List<Member>를

이렇게 화면 템플릿에 전달해도 괜찮아요. 어차피 서버 내에서 내가 원하는 데이터만 찍어서 출력을 하는 거기 때문에 괜찮은데, API를 만들 때는 이유를 불문하고 절대 엔티티를 넘기면 안돼요.

강조할게요. API를 만드실 때는 절대 엔티티를 웹으로 반환하시면 안돼요. 왜 안되냐면 API라는 건 스펙이거든요.

예를 들어서 Member 엔티티에 필드를 하나 추가해볼게요. userpassword 이게 그냥 그대로 필드가 추가되어버려요. 두 가지 문제가 있죠. 패스워드가 그대로 노출되는 문제도 있고, 또 하나는 API 스펙이 변해버려요. 엔티티에다가 로직을 추가했는데 그것 때문에 API 스펙이 변하거든요. 그럼 이제 굉장히 불안전한 API 스펙이 되고 그걸 갖다 쓰는 개발팀들은 엄청 괴롭겠죠.

그래서 엔티티는 절대 API로 외부로 노출하시면 안 돼요.

근데 템플릿 엔진에서는 선택적으로 이렇게 쓰셔도 괜찮습니다. 어차피 서버 사이드에서 다 돌고 하는 거기 때문에.

그래도 가장 권장하는 것은 이거에서 조차도 Member를 이대로 보기보다는 화면에 맞는 Data Transfer Object로 변환하셔서 반환하는 게 다시 제일 깔끔합니다.

 

자 이렇게 말씀드렸고 여기까지 회원과 관련된 기능은 다 돌려봤습니다.

이제 다음 시간에는 상품으로 넘어가 보겠습니다.

'스프링 > 실전! 스프링 부트와 JPA 활용1' 카테고리의 다른 글

상품 목록  (0) 2024.05.14
상품 등록  (0) 2024.05.14
회원 등록  (0) 2024.05.13
홈 화면과 레이아웃  (0) 2024.05.12
주문 검색 기능 개발  (0) 2024.05.11