이제 본격적으로 시작할 차례. 일단 회원가입 기능부터 만드는 게 우선!
근데 어떻게 만드는지 모른다. mysql과 연결하긴 했지만 과연 잘 된거지도 모른다.
이럴 땐 일단 따라 만드는 수밖에.
우선 Mysql에 Member테이블부터 만들어줬다. DB기초 수업을 20년도 1학기에 수강한 후(사실 이때도 공부를 거의 안함) DB는 전혀 공부한 적이 없었기 때문에..사실 이 부분도 좀 애먹었다. 그래도 어찌어찌 테이블 만드는 건 성공.
그리고 프로젝트로 돌아가 application.properties에 다음 코드를 추가해줬다.
spring.jpa.hibernate.ddl-auto=none
JPA가 자동으로 테이블을 생성하는 기능을 제공해주는데, none값을 요로코롬 멕이면 이 기능을 끈다고 한다. 지금처럼 mysql에서 이미 만든 테이블을 사용하는 경우 이렇게 한다고 함.
※ DDL : Data Definition Language의 약자. 데이터베이스 개체를 생성/삭제/변경하는 역할을 하는 놈. none값 외에도 create등을 줄 수 있는데 주로 none을 쓴다고 한다.
그러곤 Member클래스, MemberRepository클래스, MemberService클래스들을 만들어줬다.
package dokky.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.Setter;
// Entity : 실제 DB의 테이블과 매칭되는 클래스
@Getter
@Setter
@Entity
public class Member {
// Id : 해당 테이블의 primary key
@Id
@Column(name = "email")
private String email;
@Column(name = "password")
private String password;
@Column(name = "nickname")
private String nickname;
}
JPA를 쓰려면 실제 DB의 테이블과 매칭되는 클래스에게 @Entity라는 어노테이션을 멕여야 된다고 한다. 이래야 매칭되는 듯함. @Id어노테이션은 PK가 되는 컬럼에 멕이는 거고, @Column은 테이블의 어느 컬럼에 매핑되는지를 쓴다. (필드의 이름과 테이블의 칼럼 이름이 같다면 생략 가능하다고 함. 근데 이 플젝에선 그냥 썼다.)
public class JpaMemberRepository implements MemberRepository {
private final EntityManager em;
public JpaMemberRepository(EntityManager em) {
this.em = em;
}
@Override
public Member save(Member newMember) {
em.persist(newMember);
return newMember;
}
}
김영한 님의 입문강의에서는 MemberRepository란 인터페이스를 만들고 걔의 구현체로 실제 사용할 Repository를 만드는 식으로 하길래 따라해봤다. 회원가입을 먼저 만들거니까 일단 save메서드만 만들었다. 사실 김영한님 입문 강의에서는 중간에 DB를 바꾸는 작업 때문에 레포지토리를 인터페이스로 바꾼 거라고 생각해서 굳이 내가 하는 것도 레포지토리 인터페이스를 만들고 할 필요가 없다곤 생각했는데, 그래도 이런 식으로 만드는게 다형성을 활용하는 거니까 나중에 유지보수에 좋다고 생각해서 그냥 인터페이스를 만들고 구현하는 식으로 했다.
입문강의에서는 JPA를 쓰려면 EntityManager를 injection하는 구조로 만들어야 한다고 했었다. 이 놈이 db와의 통신을 내부적으로 처리해준다고 강의에 나왔었다. 그래도 이번 기회에 EntityManager가 뭔지 한 번 찾아봤다.
- Entity객체들(@Entity어노테이션이 멕여진)을 관리하며 이놈들을 실제 DB의 테이블과 매핑하여 데이터를 CRUD해주는 객체
- 내부에 PersistenceContext라는 논리적 영역을 두어, 이를 통해 Entity들을 관리함
- 김영한님 강의피셜 data-jpa라이브러리를 땡겨오면 부트가 알아서 EntityManager를 생성해준다고 함..! (EntityManager가 Bean으로 자동으로 등록되는 듯)
...음..Entity들을 내부적으로 관리해준다는 것만 여기선 이해하고 넘어갈 수 있을 듯 하다.
아! 참고로 JPA를 정의한 게 javax.persistence패키지라고 한다.
@RequiredArgsConstructor
// jpa를 통한 모든 데이터 변경은 트랜잭션 안에서 실행!
@Transactional
public class MemberService {
private final MemberRepository memberRepository;
public String join(Member newMember) {
memberRepository.save(newMember);
return newMember.getEmail();
}
}
@Transactional은 클래스나 메서드에 붙여줄 경우, 해당 범위 내 메서드가 트랜잭션이 되도록 보장해주는 어노테이션이라고 한다. 김영한님 강의피셜 JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 실행돼야 한다고 한다. 그래서 저 어노테이션을 붙여줬다.
음..모르는게 너무 많다. 이걸 다 하나하나 짚으면서 이해하고 넘어가긴 지금은 무리다. 일단 하면서 나중에 "이게 이거였구나"라고 알아야 되는 영역같다. ㅠ;
암튼 이제 Config클래스를 만들어서 이 놈들을 Bean으로 등록해줬다.
@Configuration
public class SpringConfig {
private final EntityManager em;
@Autowired
public SpringConfig(EntityManager em) {
this.em = em;
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new JpaMemberRepository(em);
}
}
근데 갑자기 의문이 생겼다.
"왜 Member클래스는 Bean으로 등록하지 않지?"
강의를 통해 내가 이해한 것은, 스프링 프로젝트에서 작성한 클래스들을 스프링에서 쓸려면 빈으로 반드시 등록해야 한다는 것이었기 때문이다. 근데 강의를 돌아봐도, Member클래스는 Bean으로 등록을 안 했다.
빈으로 등록되는 애들은 싱글턴으로 등록되기 때문에 Member를 Bean으로 하면 안 되는 건 이해되지만, 내가 알고 있던 전제인 "스프링에서 쓸려면 빈으로 반드시 등록해야 함"이 잘못됐다는 걸 깨달았다. 일단 지금은 스프링 컨테이너에 의해 만들어지는 객체 = 빈 이 정도만 이해하고 넘어가야 할 것 같다.
이후엔 간단하게 회원가입을 위한 폼을 띄우는 페이지 만들어주고, 회원가입 버튼을 누르면 /account/signup으로 POST요청을 보내게끔 만들었다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>회원가입</title>
</head>
<body>
<div>
<form action="account/signup" method="POST">
<div>
<label for="email">이메일</label>
<input type="email" name="email" id="email">
</div>
<div>
<label for="password">비밀번호</label>
<input type="password" name="password" id="password">
</div>
<div>
<label for="nickname">닉네임</label>
<input type="text" name="nickname" id="nickname">
</div>
<button type="submit">회원가입</button>
</form>
</div>
</body>
</html>
이제 이 요청을 처리하는 메서드를 컨트롤러에 작성하면 되는데, 여기서 또 의문이 생겼다. 강의내용에서는 따로 MemberForm이란 클래스를 만들고 이걸 파라미터로 받는 식으로 회원가입 메서드를 만들었다. 근데 MemberForm클래스는 Member클래스와 필드가 똑같고 단순히 Getter, Setter만 있다.
@PostMapping("/signup")
public String signup(MemberForm memberForm) {
Member newMember = new Member();
newMember.setEmail(memberForm.getEmail());
newMember.setPassword(memberForm.getPassword());
newMember.setNickname(memberForm.getNickname());
memberService.join(newMember);
return "success";
}
대충 이런 식으로 흐름이 진행되던데..어차피 MemberForm이랑 Member랑 필드가 똑같으면 파라미터를 MemberForm이 아니라 Member로 해도 괜찮지 않나?란 생각이 들었다. 이건 그냥 바로 실험으로 옮겨봤다. 되나 안되나..
아래 코드로 바꾸고 실험해봤다. 두구두구두구..
@PostMapping("/signup")
public String signup(Member newMember) {
memberService.join(newMember);
return "success";
}
? 결과는 성공이었다. 뭐지 그럼 왜 강의에선 Member가 아니라 MemberForm으로 받던거지..했는데 이 글을 쓰다가 그걸 알게 됐다.
실제 멤버클래스가 가지는 필드 중에 회원가입 폼에서 작성하지 않는 필드가 있을 수 있기 때문이다! 내가 만드는 이 플젝에서는 멤버클래스의 필드 모두를 온전히 멤버폼이 가지고 있으니까 상관없지만, 강의에 나온 예제에서는 멤버가 가지는 id필드값이 폼에서는 작성하지 않는 필드였으니까 멤버폼을 파라미터로 쓰던 것.
오키..근데 아직도 의문인 건 MemberForm은 왜 빈으로 등록 안 하는건지 모르겠다. 으음..스프링이 알아서 MemberForm으로 바인딩해주고 이걸 컨트롤러에 인자로 넘겨주는거면, 내가 이해한 바로는 MemberForm이 빈으로 등록돼있어야 한다. (그래야만 스프링이 관리를 해주는 거고, 관리를 해준다는 것은 그 빈을 꺼내서 주입해준다 등으로 이해했음)
모르는 것 투성이다..
MemberForm에 대해 더 알아봤다. 저렇게 Client ~ Controller 이런 식으로 각 계층 간에서 단순 데이터 전달을 위한 목적으로 사용되는 객체를 DTO(Data Transfer Object)라고 부른다고 한다. 한 마디로 각 계층끼리 주고받는 편지의 느낌. 로직을 갖고 있지 않은 순수한 데이터 객체로 메서드로는 getter/setter 만을 가진다고 한다.
이제 내가 헷갈렸던 거는 멤버폼(DTO)이 아니라 멤버클래스(Entity)를 써서 해도 되던데 왜 멤버폼을 쓰지? 였다. 물론 내가 위에서 기술한 이유인 "실제 멤버클래스가 가지는 필드 중에 회원가입 폼에서 작성하지 않는 필드가 있을 수 있기 때문"도 맞지만, 더 나아가 역할의 분리를 위해서 DTO와 Entity로 구분하는 것이기도 하다. Entity클래스는 단순히 데이터를 담는 것을 넘어 실제 DB와 관련된 중요한 역할을 하는 반면 DTO는 목적 자체가 '전달'이라 그냥 일회성으로만 쓰이기 때문에, 이런 역할 분리를 위해 MemberForm과 Member로 분리해서 쓰던 것. 또한 DTO와 Entity의 생명주기가 다르다는 이유도 있다고 함.
'PROJECT > 개발일지' 카테고리의 다른 글
[백엔드 야생 도전일기] 스프링으로 회원가입/로그인기능 만들어보기 (4) (0) | 2023.01.31 |
---|---|
[백엔드 야생 도전일기] 스프링으로 회원가입/로그인기능 만들어보기 (3) (0) | 2023.01.29 |
[백엔드 야생 도전일기] 스프링으로 회원가입/로그인기능 만들어보기 (1) (0) | 2023.01.27 |
[React project] EP04. 항상 볼 수 없는 사이 - 리액트에서의 조건부 렌더링 및 기본적인 css 작업 (0) | 2022.03.15 |
[React project] EP03. 너 분명 변했다면서. 근데 왜 그대로인건데! - 리액트의 setter함수는 비동기적으로 동작한다 (0) | 2022.03.04 |