...
@Controller
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
...
@GetMapping("/members")
public String list(Model model){
List<Member> members = memberService.findMembers();
model.addAttribute("members", members);
return "members/memberList";
}
Model) 객체에 보관findMembers 메서드를 사용해서 데이터를 끌어옴model에 담아서 화면에 넘김members 아래에 memberList라는 html 파일을 추가하여 데이터 조회를 위한 뷰를 만듦templates/members/memberList.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header" />
<body>
<div class="container">
<div th:replace="fragments/bodyHeader :: bodyHeader" />
<div>
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>이름</th>
<th>도시</th>
<th>주소</th>
<th>우편번호</th>
</tr>
</thead>
<tbody>
<tr th:each="member : ${members}">
<td th:text="${member.id}"></td>
<td th:text="${member.name}"></td>
<td th:text="${member.address?.city}"></td>
<td th:text="${member.address?.street}"></td>
<td th:text="${member.address?.zipcode}"></td>
</tr>
</tbody>
</table>
</div>
<div th:replace="fragments/footer :: footer" />
</div> <!-- /container -->
</body>
</html>
타임리프는 위와 같이 HTML 태그를 그대로 가져다 쓰는 것이 큰 장점
모델에서 담아서 넘긴 members 리스트가 ${members}에 바인딩됨
<tr th:each="member : ${members}">와 같이 th:each 문법을 통해 루프를 돌리면서 데이터를 화면에 뿌리게 됨
${member.address?.city}처럼 타임리프에서 ?를 사용하면 null을 무시함
address가 없으면, then city를 조회해오지 않는다는 의미위 코드에서 엔티티를 손을 대지 않아도 되는 상황이기 때문에 멤버 객체를 조회해올 때 member entity를 그대로 출력했지만, dto로 변환을 해서 화면에 필요한 데이터들만 가지고 출력하는 것이 권장됨
MemberForm) 없이 엔티티(Member)를 직접 등록과 수정 화면에서 사용해도 됨JPA를 사용할 때 가장 주의해야 할 점은 엔티티를 최대한 순수한 상태로 유지해야 하는 것API를 만들 때는 절대 엔티티를 웹으로 반환하면 안됨API는 스펙이기 때문에, 엔티티의 로직을 추가하게 되면 그로 인해 API 스펙이 변하게 됨API 스펙이 되고, 그것을 활용하는 개발팀이 괴로워짐API로 외부에 노출하면 안됨
- 실무에서는 엔티티는 핵심 비즈니스 로직만 가지고 있고, 화면을 위한 로직은 없어야 함
- 화면이나 API에 맞는 폼 객체나 DTO를 사용해야 함
- 화면이나 API 요구사항을 이것들로 처리하고, 엔티티는 최대한 순수하게 유지해야 함

