Loading...

Spring/Blog-V2 / / 2022. 3. 23. 20:13

스프링 41강. 회원가입 코드 리팩토링

반응형

회원가입은 INSERT라서 post 요청을 해야 한다.
post요청은 form태그로도 가능한데
PUT, DELETE 요청은 fetch로 하고,

GET, POST 요청은 form으로 하면
일관성이 없으니까 전부 다 fetch 요청으로 통일하자.

 

form태그는 페이지를 응답받기 때문에 전체 새로고침이 일어나고

fetch는 AJAX로 부분 리로드 된다.

 

 

User 엔티티에서 만든 제약조건을 참고하여

프론트에서 막아주자!!

@AllArgsConstructor
@NoArgsConstructor
@Data
@EntityListeners(AuditingEntityListener.class)
@Entity
public class User {

    @Id // PK
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(unique = true, nullable = false, length = 12) // unique 제약조건, length 기본 255바이트
    private String username;

    @Column(nullable = false, length = 12)
    private String password;

    @Column(nullable = false, length = 30)
    private String email;

    @Column(nullable = false, length = 300)
    private String address; // API 주소 라이브러리 사용

    // 모든 Entity 공통
    @CreatedDate
    private LocalDateTime createDate;
    @LastModifiedDate
    private LocalDateTime updateDate;
}

 

dom에 걸어주는 id명은 데이터베이스 테이블 컬럼명과 동일하게 만들어주는 게 좋다.

 

{{> /layout/header}}

<!-- 컨테이너 시작 -->
<div class="container mt-3">

  <!-- 회원가입 폼 시작 -->
  <form>
    <div class="mb-3 mt-3">
      <input id="username" type="text" class="form-control" placeholder="Enter username" maxlength="12" required>
    </div>
    <div class="mb-3">
      <input id="password" type="password" class="form-control" placeholder="Enter password" maxlength="12" required>
    </div>
    <div class="mb-3">
      <input id="email" type="email" class="form-control" placeholder="Enter emails" maxlength="30" required>
    </div>
    <div class="mb-3">
      <input id="address" type="text" class="form-control" placeholder="Enter address" maxlength="300" required>
    </div>

    <button id="btn-join" type="button" class="btn btn-primary">회원가입</button>
  </form>
  <!-- 회원가입 폼 끝 -->

</div>
<!-- 컨테이너 끝 -->

{{> /layout/footer}}

 

회원가입 버튼에 onclick 메서드를 달지 않고

자바스크립트로 fetch 요청할 것이다.

 

다음과 같은 로직을 참고해

자바스크립트 코드를 작성한다.

 

1. 회원가입 하고싶어?
post로 /join 요청해!

username, password, email, address

2. 너 데이터 전송할 때 body에 담는 데이터 전부 json으로 던져!

3. 서버 쪽 응답은 아래와 같아.
여기서 code가 1이면 성공, -1이면 실패야!
{
  "code":1,
  "msg":"성공",
  "data":그때그때다름
}

 

 

<script>
  // 1. 이벤트 리스너
  $("#btn-join").click(() => {
    join();
  });

  // 2. 기능
  // async function join() {
  // }
  let join = async () => {

    // (1) username, password, email, address 찾아서 자바스크립트 오브젝트로 만들기
    let userDto = {
      username: $("#username").val(),
      password: $("#password").val(),
      email: $("#email").val(),
      address: $("#address").val()
    }

    // (2) 자바스크립트 오브젝트 -> JSON 변환 (통신의 표준이 JSON!!)
    // let userJson = JSON.stringify(userDto);

    // (3) fetch 요청
    let response = await fetch("/join", {
      method: 'POST',
      body: JSON.stringify(userDto),
      headers: {
        'Content-Type': 'application/json;charset=utf-8'
      }
    });

    let responseObject = await response.json();

    // (4) 회원가입이 잘되면 알림창을 띄우고 로그인 페이지로 이동
    if (responseObject.code == 1) {
      alert("회원가입에 성공하였습니다.");
      location.href = "/loginForm";
    } else {
      alert("회원가입에 실패하였습니다.\n 실패 이유 : " + responseObject.msg);
    }
  }

</script>

 


 

이제 실제 회원가입을 실행하는 컨트롤러를 만들러 가보자.

자바스크립트는 html 파일을 리턴 받는 게 의미가 없다.

데이터를 리턴 받아야 하므로 UserApiController에 만들어주자.

 

보통 데이터를 리턴하는 api 컨트롤러 주소에는

앞에 /api가 붙는다.

 

공통 응답에 대한 데이터 트랜스퍼 오브젝트(DTO)를 만들어준다.

 

api 폴더 안에!!

 

package site.metacoding.blogv2.web.api.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor
@Data
public class ResponseDto<T> {
    private Integer code;
    private String msg;
    private T data;
}

 

사용자로부터 데이터를 받는데

username, password, email, address만 받기 위해

user 오브젝트로 받지 않는다.

 

그럼 null 값도 같이 들어오기 때문이다.

나중에 유효성 검사하기에 힘들다.

 


 

데이터를 하나하나 확인하며 null 체크 하는 것 보다

박스가 꽉 찼는지 확인하는게 null 체크하는데 더 편할것이니까 Dto를 만들어주는 것이다.

 

Dto를 만들어두면 공통로직을 만들 수 있어서 유효성 체크에 편하다.

 


 

Dto를 하나 더 만들어주자.

 

createDate, updateDate, id는 필요 없고

필요한 데이터만 받고 수집하기 위한 오브젝트(JoinDto)를 만든 것이다.

package site.metacoding.blogv2.web.api.dto.user;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor
@Data
public class JoinDto {
    private String username;
    private String password;
    private String email;
    private String address;
}

 

이제 이걸 컨트롤러에서 받아보자.

그냥 오브젝트(JoinDto)를 바로 받을 수는 없다.

 

스프링 파싱 전략을 변경해주어야 한다.

 


 

스프링 기본 파싱 전략

 

(MIME 타입) x-www-form-urlencoded

 

ex) username=ssar&password=1234

 

위와 같은 타입의 데이터는 스프링에서

아래와 같은 오브젝트를 만들어서 받을 수 있다.

class User {
	private String username;
    private String password;
}

 

스프링 파싱 전략 변경 @RequestBody

 

(MIME 타입) application/json

 

ex) {                          

"username":"ssar",

"password":"1234"

}                     

 

위와 같은 타입의 데이터는 스프링에서

@RequestBody를 사용해 받을 수 있다.

 


 

joinForm에서 자바스크립트로 json 데이터를 전송해오는데

컨트롤러에서 스프링 기본 파싱 전략이 x-www-form-urlencoded 타입을 파싱 하기 때문에

json데이터를 파싱 하지 못한다.

 

@RequestBody를 붙여서 json을 파싱 할 수 있게 기본 파싱 전략을 바꿔주자.

 

 


 

@PostMapping("/api/join")
public ResponseDto<String> join(@RequestBody JoinDto joinDto) {

    userService.회원가입(joinDto);

    return null;
    // return new ResponseDto<String>(1, "성공", null);
}
public void 회원가입(JoinDto joinDto) {
        
}

 

이제 서비스에서 userRepository.save(joinDto)를 할 수 없다.

save 메서드는 리턴을 User 타입으로 해줘야 하기 때문이다.

 

영속성 컨텍스트가 User 오브젝트만 관리할 수 있기 때문이다.

우리가 JpaRepository<User, Integer>라고 설정해놨잖아!

 

통신을 위해 Dto를 사용했고

joinDto를 User 타입으로 바꿔주어야 한다.

 

외부에서 데이터를 받아서 다시 DB에 넣을 수 있는 타입의 오브젝트로 바꾸는 게 DTO의 역할이다.

 

package site.metacoding.blogv2.web.api.dto.user;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import site.metacoding.blogv2.domain.user.User;

// DTO : Data Transper Object (통신으로 전달하거나 받는 오브젝트를 엔티티 타입으로 변환)

@AllArgsConstructor
@NoArgsConstructor
@Data
public class JoinDto {
    private String username;
    private String password;
    private String email;
    private String address;

    // Entity로 변환해서 리턴
    public User toEntity() {
        User user = new User();
        user.setUsername(this.username);
        user.setPassword(this.password);
        user.setEmail(this.email);
        user.setAddress(this.address);

        return user;
        // return new User(null, username, password, email, address, null, null);
    }
}

 

 

api 컨트롤러는 무조건 응답이 잘되었다고 바디에 리턴해주어야 한다.

서비스에서 save 하면 insert 하고, insert 된 결과를 jpa가 리턴해준다.

리턴된 데이터가 필요하면 받으면 되고, 필요 없으면 안 받으면 된다.

 

데이터를 받아야 하면 responseDto의 data 자리에 넣어주면 된다.

타입 알아서 맞추고!!

@Transactional
public void 회원가입(JoinDto joinDto) {
    // save하면 DB에 INSERT하고 INSERT된 결과를 다시 return 해준다. -> jpa가 리턴해줌
    userRepository.save(joinDto.toEntity());
}
@PostMapping("/api/join")
public ResponseDto<String> join(@RequestBody JoinDto joinDto) {

    userService.회원가입(joinDto);

    return new ResponseDto<String>(1, "성공", null);
}

 

 

 

 

[출처]

 

https://cafe.naver.com/metacoding

 

메타코딩 : 네이버 카페

코린이들의 궁금증

cafe.naver.com

메타 코딩 유튜브

https://www.youtube.com/c/%EB%A9%94%ED%83%80%EC%BD%94%EB%94%A9

 

메타코딩

문의사항 : getinthere@naver.com 인스타그램 : https://www.instagram.com/meta4pm 깃헙 : https://github.com/codingspecialist 유료강좌 : https://www.easyupclass.com

www.youtube.com

 

반응형