웹 계층 개발 - 회원 등록, 목록 조회
위의 사진은 강의를 들으면서 개발할 기능들이다. 회원가입을 하고 상품 등록을 하고 주문을 할 수 있는 웹 애플리케이션을 만들 것이다. 이번 포스팅에서는 회원가입을 할 수 있는 회원가입 폼을 만들 것이다.
html과 css 코드가 있긴 한데 스프링과 스프링부트, JPA를 공부하는 과정이기 때문에 프론트엔드 개발쪽은 직접 코드를 작성하지 않고 복사, 붙여넣기를 사용했다.
MemberController
@Controller
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
@GetMapping("/members/new")
public String createForm(Model model) {
model.addAttribute("memberForm", new MemberForm());
return "members/createMemberForm";
}
홈 화면에서 회원 가입 버튼을 누르면 http://localhost:8080/members/new로 이동하는 것으로 html을 작성해놨다. GetMapping을 통해 /members/new로 이동하고, 모델을 통해 model.addAttribute로 html에 MemberForm 객체를 넘겨주었다. return을 통해 members/createMemberForm으로 연결시켜주면 resource에 있는 templates에서 createMemberForm.html을 찾아서 해당 html을 연결시켜준다.
creatMemberForm.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header" />
<style>
.fieldError {
border-color: #bd2130;
}
</style>
<body>
<div class="container">
<div th:replace="fragments/bodyHeader :: bodyHeader"/>
<form role="form" action="/members/new" th:object="${memberForm}" method="post">
<div class="form-group">
<label th:for="name">이름</label>
<input type="text" th:field="*{name}" class="form-control" placeholder="이름을 입력하세요"
th:class="${#fields.hasErrors('name')}? 'form-control fieldError' : 'form-control'">
<p th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Incorrect date</p>
</div>
<div class="form-group">
<label th:for="city">도시</label>
<input type="text" th:field="*{city}" class="form-control" placeholder="도시를 입력하세요">
</div>
<div class="form-group">
<label th:for="street">거리</label>
<input type="text" th:field="*{street}" class="form-control" placeholder="거리를 입력하세요">
</div>
<div class="form-group">
<label th:for="zipcode">우편번호</label>
<input type="text" th:field="*{zipcode}" class="form-control" placeholder="우편번호를 입력하세요">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<br/>
<div th:replace="fragments/footer :: footer" />
</div> <!-- /container -->
</body>
</html>
createMemberForm이다. 내가 직접 짠 코드는 아니지만 간단히 요약을 하자면 타임 리프를 사용했고, th:object="${memberForm}"으로 memberForm의 객체에 th:field=*{city} 등으로 사용자가 화면에서 입력하는 것을 데이터에 넘길 수 있는 기능을 작동한다.
회원가입 버튼을 눌렀을 때 나오는 화면이다. 이름, 도시, 거리, 우편번호를 th:filed로 받아온다.
PostMapping으로 회원가입 완료
@PostMapping("/members/new")
public String create(@Valid MemberForm form, BindingResult result) {
// BindingResult는 valid에서 오류가 발생했을 때 오류를 가지고 동작을 해줄 수 있게 해줌
// 스프링이 MemberForm에 있는 message를 출력해줌
// 물론 thymeleaf로 html에서 에러 메세지 출력하게 해서 가능한거
if (result.hasErrors()) {
return "members/createMemberForm";
}
Address address = new Address(form.getCity(), form.getStreet(), form.getZipcode());
Member member = new Member();
member.setName(form.getName());
member.setAddress(address);
memberService.join(member);
return "redirect:/";
}
아까는 @GetMapping으로 /members/new에 연결했다. 하지만 이번에는 @PostMapping으로 연결한다. @GetMapping은 Get방식으로 페이지를 보여주는 기능이었다. @PostMapping이란 사용자가 페이지에서 값을 입력하고 버튼을 눌렀을 때의 동작을 Mapping하는 것이다. /members/new라는 페이지에서 버튼을 누르기 때문에 @PostMapping("/members/new")로 PostMapping이 진행된다.
일단 if문을 뒤로하고 보면 member을 만든 다음 name과 address를 set으로 입력하고 Service 계층에서 만들었던 join을 통해 member를 저장해주면 된다. return "redirect:/"가 되어있는데 redirect는 다시 보낸다는 뜻이고 / 로 보내는 것이기 때문에 회원 가입을 하면 홈화면으로 돌려보내 준다는 뜻이다. 그냥 화면을 이동시켜주는 것이 아니라 리다이렉트하는 것이다.
지금까지 설명해준 것은 memberForm의 조건에 부합해서 정상적으로 예외없이 회원가입될 때의 절차이다.
MemberForm
@Getter @Setter
public class MemberForm {
@NotEmpty(message = "회원 이름은 필수입니다")
private String name;
private String city;
private String street;
private String zipcode;
}
위의 코드는 MemberForm객체인데, 여기에 name위에 @NotEmpty라는 애노테이션이 붙었다. 이 뜻은 이름은 무조건 공백이 될 수 없다는 뜻이다. 만약 이름을 공백으로 둔 상태에서 submit 버튼을 누르게 된다면 에러를 발생해야한다.
코드를 보면 파라미터안에 @Valid와 BindingResult가 들어가있다. 우선 Valid는 MemberForm에 있는 @NotEmpty처럼 조건이 있을 때 조건에 부합하는지 검증하는 애노테이션이다. NotEmpty가 있는데 회원 이름을 공백으로 두고 submit을 하게 된다면 검증에서 걸리게 된다. BindingResult는 valid에서 검증에 실패했을 때 검증을 가지고 에러 메시지를 출력하게 해준다. MemberForm에 message가 있는데, createMemberForm에 있는
th:class="${#fields.hasErrors('name')}? 'form-control fieldError' : 'form-control'">
<p th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Incorrect date</p>
에러 발생 코드로 인해서 MemberForm에 있는 메시지가 사용자에게 보여지는 것이다.
회원 목록 조회
회원 가입을 한 회원들의 목록을 조회하는 기능을 만들 것이다.
@GetMapping("/members")
public String list(Model model) {
List<Member> members = memberService.findMembers();
model.addAttribute("members", members);
return "members/memberList";
}
코드는 간단한데, MemberService에서 findMembers를 한 다음, 그 객체를 모델에 넘겨주면 된다.
Service 계층에서 findMembers는 JPA를 이용해 모든 member를 찾아오는 기능이었다.
여기서 member 엔티티를 그대로 모델에 넘겨주었는데 이렇게하면 복잡한 프로젝트에서는 엔티티가 복잡해지기 때문에 실무에서 잘 사용하지 않는다고 한다. 회원 가입때 했던 것처럼 MemberForm 같은 컨트롤러 전용 DTO를 만들어서 엔티티를 그대로 보내는 것이 아니라 따로 만든 DTO로 변환해서 화면에 꼭 필요한 데이터들만 모델에 넘겨야한다.
첫 번째 회원을 가입시키고 회원 목록을 들어가보니 첫 번째 회원이 잘 나온 것을 확인할 수 있었다.