회원가입은 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
메타 코딩 유튜브
https://www.youtube.com/c/%EB%A9%94%ED%83%80%EC%BD%94%EB%94%A9