스프링데이터 + JPA/웹 애플리케이션 개발

21. 회원 등록

sdafdq 2023. 11. 9. 12:18

회원 등록 부분의 컨트롤러, 문서를 개발할 것이다.

 

@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";
    }
}

보통 컨트롤러에서 service를 쓰니 일단 주입받는다. (@RequiredArgsCons~~ 저걸로 final 인자로 있는 생성자 생성해주고, 그럼 알아서 빈에서 주입됨.)

 

/members/new 라는 url로 왔을 때,

템플릿 중 

members/createMeberForm.html을 찾아서 거기다 렌더링 해 준다.

그런데 모델에 memberForm이라는 것에 값을 MemberForm()

@Getter @Setter
public class MemberForm {
    @NotEmpty(message = "회원 이름은 필수 입니다")
    private String name;
    
    private String city;
    private String street;
    private String zipcode;
}

이렇게 name만 비어있으면 안되는 Form용 빈 객체를 생성해서 넣어준다.

 

이렇게 빈 객체로 넣는 이유가 아마 저

createMemberForm.html이 값을 읽게 하려는 속셈 같다.

왜냐하면 뭔가 validation 하는 데, 조건에 불만족하는 값을 하나 넣었다고 모든 걸 초기화 시킬 수도 있으니,

그것 빼고 담아서 저기에 다시 보내주려는 속셈 같다.

 

여튼 모델에 넘겼으니, 저 모델은 디스패처서블릿(이 컨트롤러를 호출한 컨트롤러)에서 넣어주는 거므로, 저 Model의 주소는 DispatcherServlet이 가지고 있기에 랜더링 할 때 같이 뷰에다가 넘겨준다. 그래서 뷰도 읽을 수 있다.

<!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-controlfieldError' : '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>

이렇게 모델을 통해 "memberForm"이라는 이름으로 넘겨받은 값, 이번 경우 객체를 사용할 수 있다. (GetMapping 에서는 그냥 빈 객체이므로 memberForm 내부의 필드값 자체는 다 비어있다.)

${모델에있는값 즉 키} 이렇게 사용할 수 있고,

저 *{변수명}

이거는 저렇게 th:object = ${모델로부터넘겨받은것의키} 이렇게 오브젝트 지정해 주면 저 오브젝트를 계속 사용하겠다는 것이다.

그래서 저렇게 * 하면 저 오브젝트의 변수를 가리키는 것이다.

 

th:field는 저거 input 태그의 id, name을 써주는 거다. 변수명으로 맞춰주는 듯. id랑 name은 관례상 같음.

 

 

 

 

저 form 보면 /members/new 로 가게끔 한다.

action해서 가는 게 /members/new인데, 보면 method = post로 되어 있다.

즉, PostMapping("members/new") 를 만들고, 거기에 인증해서 조건에 만족하지 않으면 다시 /members/new 로 보내는 데 그 때는 사용자가 입력한 값들을 MemberForm에 담고 그걸 넘겨서 다시 입력해야 하지 않게끔 하려는 의도 같다.

 

만약 validation을 통과하면 저 받은 MemberForm을 가지고 DB에 등록 후, redirect 하겠지. 

 

 

 

 

@PostMapping("/members/new")
public String joinForm(@Valid MemberForm form, BindingResult result){

    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:/";
}

 

이렇게 PostMapping 했다.

MemberForm으로 받아오고, 저기서 @Vaild 할 시 직역 그대로 검증인데, 오류가 나면 BindingResult 말 그대로 바인딩에 대한 결과인데, 검증오류 시 저 result에 담긴다.

뭘 검증하냐,

@Getter @Setter
public class MemberForm {
    @NotEmpty(message = "회원 이름은 필수 입니다")
    private String name;
    
    private String city;
    private String street;
    private String zipcode;
}

여기 보면 @NotEmpty 애노테이션이 있다.

jakarta(javax)의 기능인데 말 그대로 비어있으면 안된다는 거다.

 

 

뭐 많다.

 

 

여튼 여기서는 

@Getter @Setter
public class MemberForm {
    @NotEmpty(message = "회원 이름은 필수 입니다")
    private String name;
    
    private String city;
    private String street;
    private String zipcode;
}

이름이 비어있으면 안된다.

 

비어 있을 시,

@PostMapping("/members/new")
public String joinForm(@Valid MemberForm form, BindingResult result){
    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:/";
}

 

result에 에러가 들어가고,

저렇게 에러 있는지 없는 지 검사해서 들어갈 수 있다.

MemberForm 저렇게 인자로 받는 건 Model에 넣어준다. 저게 @ModelAttribute 생략됐던 거였나?

 

여튼 그래서 저렇게 BindingResult를 가지면 코드를 그대로 진행 해 주는데,

타임리프에서,

<input type="text" th:field="*{name}" class="form-control"
       placeholder="이름을 입력하세요"
       th:class="${#fields.hasErrors('name')}? 'form-controlfieldError' : 'form-control'">
<p th:if="${#fields.hasErrors('name')}"
   th:errors="*{name}">Incorrect date</p>

이 부분.

 

th:class는 타임리프 클래스,

# 샾 붙은 게 지정된 변수? 같은 것 들인데,

그 중 fields가 BindingResult인 듯.

거기에 'name'이라는 에러 있으면, 이거는 아마 기본값은 필드명으로 들어가는 듯.

저거는 삼항연산자로, th:class니까 클래스 지정

 

그리고 마찬가지로 th:if만약 저 조건이 참이면,

저 p를 보이는 듯. 거짓이면 안 보이고.

근데 보이는 데, th:errors 이거는 에러내용을 뽑아오는 건데, 저 *은 아까 말했듯이

<form role="form" action="/members/new" th:object="${memberForm}"

저 object 저거임.

그 에러 내용은 어디서 뽑아온거냐,

@Getter @Setter
public class MemberForm {
    @NotEmpty(message = "회원 이름은 필수 입니다")
    private String name;
    
    private String city;
    private String street;
    private String zipcode;
}

여기 message.

 

 

 

보통 이렇게 폼은 따로 클래스를 만듦.

 

엔티티에 그대로 받아오는 건 스펙상 안 맞는 경우가 많음. DB에 더 연관되어 있기 때문에. 그냥 Form용 클래스 따로 만들어서 필요한 것만 뽑아서 엔티티에 맞추는 게 훨씬 좋음.

 

 

'스프링데이터 + JPA > 웹 애플리케이션 개발' 카테고리의 다른 글

23. 상품 등록  (0) 2023.11.10
22. 회원 목록 조회  (0) 2023.11.10
20. 웹 계층 개발  (0) 2023.11.09
19. 주문 검색 기능 개발  (0) 2023.11.09
18. 주문 기능 테스트  (0) 2023.11.08