신규 파일 생성
MemberController.java
package com.api.westmall.controller;
import com.api.westmall.common.CommonResponse;
import com.api.westmall.form.MemberForm;
import com.api.westmall.service.MemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
@RequiredArgsConstructor
@RequestMapping("/member")
public class MemberController {
private final CommonResponse commonResponse;
private final MemberService memberService;
@PostMapping("")
public ResponseEntity<?> user(@ModelAttribute @Valid MemberForm memberForm) {
memberService.saveUser(memberForm);
return commonResponse.send();
}
}
@Valid 어노테이션을 사용하여 validation 에러 처리도 적용하였다. 아래 포스팅을 참고하자.
https://devfunny.tistory.com/428
MemberService.java
package com.api.westmall.service;
import com.api.westmall.entity.Member;
import com.api.westmall.form.MemberForm;
import com.api.westmall.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
private final ModelMapper modelMapper;
/**
* 회원 저장
* @param memberForm
*/
public void saveUser(MemberForm memberForm) {
Member member = modelMapper.map(memberForm, Member.class);
memberRepository.save(member);
}
}
ModelMapper을 사용하여 memberForm -> Member 로 매핑하였고, 그 결과로 데이터를 저장한다.
MemberForm.java
package com.api.westmall.form;
import com.api.westmall.entity.Gender;
import lombok.Data;
import lombok.NonNull;
import org.hibernate.validator.constraints.Length;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
@Data
public class MemberForm {
private Long idx;
@NotBlank
private String userId;
@NotBlank
@Length(min = 8, max = 50)
private String password;
@NotBlank
private String userName;
private Gender gender;
@Email
@NotBlank
private String email;
}
Member.java
package com.api.westmall.entity;
import lombok.*;
import javax.persistence.*;
@Entity
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "member")
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idx;
@Column(unique = true)
@NonNull
private String userId;
@NonNull
private String password;
@NonNull
private String userName;
/* enum type */
@Enumerated(value = EnumType.STRING)
private Gender gender;
@NonNull
private String email;
}
application.yml
JPA 사용을 위한 yml 설정을 추가한다.
spring:
datasource:
hikari:
jdbc-url: jdbc:mysql://127.0.0.1:3306/westmalldb?characeterEncoding=UTF-8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
username: westmall
password: westmall
jpa:
show-sql: true # query 출력
hibernate:
ddl-auto: create # 항상 새로 생성하고, 종료되면 자동으로 drop 처리한다. (운영에선 none 대부분)
properties:
format_sql: true # query format
단위테스트 코드
MemberServiceTest.java
package com.api.westmall.service;
import com.api.westmall.entity.Gender;
import com.api.westmall.entity.Member;
import com.api.westmall.form.MemberForm;
import com.api.westmall.repository.MemberRepository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.jupiter.api.Assertions.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
class MemberServiceTest {
@Autowired
private MemberService memberService;
@Autowired
private MemberRepository memberRepository;
@Test
@DisplayName("회원 등록이 정상적으로 저장되는지 체크")
@Transactional
public void add_member_success() throws Exception {
MemberForm memberForm = new MemberForm();
String userId = "test001";
memberForm.setUserId(userId);
memberForm.setUserName("테스트");
memberForm.setPassword("root");
memberForm.setGender(Gender.FEMALE);
memberForm.setEmail("test@naver.com");
memberService.saveUser(memberForm);
Member member = memberRepository.getById(1L);
Assertions.assertThat(member.getUserId()).isEqualTo(userId);
}
}
저장 이후 getById() 메서드를 사용하여 방금 등록한 데이터를 가져와서 userId 일치 여부를 확인하였다.
참고로 여기서 처음에 에러가 발생하였는데 짚어보자.
@Transactional 없는 버전
...
@Test
@DisplayName("회원 등록이 정상적으로 저장되는지 체크")
public void add_member_success() throws Exception {
MemberForm memberForm = new MemberForm();
String userId = "test001";
memberForm.setUserId(userId);
memberForm.setUserName("테스트");
memberForm.setPassword("root");
memberForm.setGender(Gender.FEMALE);
memberForm.setEmail("test@naver.com");
memberService.saveUser(memberForm);
Member member = memberRepository.getById(1L);
Assertions.assertThat(member.getUserId()).isEqualTo(userId);
}
...
아래 에러가 발생한다.
could not initialize proxy [com.api.westmall.entity.Member#1] - no Session
디버깅을 해보면, 마지막에 getById() 해온 결과 이후 member.getUserId()를 해오는 경우에 아래 에러가 발생한다.
org.hibernate.LazyInitializationException
이 에러는 지연 로딩을 하려고 할때, 이미 세션이 사라져서 지연 로딩을 못할 경우 생기는 에러다. 해결방안은 2가지가 있다.
1) JPA 에서 지연로딩을 하려면 영속 상태로 묶여야한다. 이를 해결하기위해 @Transactional 어노테이션을 추가한다.
2) getById() 대신 findById()를 사용하자.
Member member = memberRepository.findById(1L).get();
getById()는 내부적으로 EntityManager.getReference() 메소드를 호출하여 엔티티를 직접 반환하지 않고 프록시 객체를 반환한다. 따라서 프록시를 실제로 사용하기 전까진 DB에 접근하지 않는다. (지연로딩)
findById()는 영속성 컨텍스트의 1차 캐시가 없으면 DB에서 데이터를 조회해온다.
@Transactional 있는 버전
...
@Test
@DisplayName("회원 등록이 정상적으로 저장되는지 체크")
@Transactional
public void add_member_success() throws Exception {
MemberForm memberForm = new MemberForm();
String userId = "test001";
memberForm.setUserId(userId);
memberForm.setUserName("테스트");
memberForm.setPassword("root");
memberForm.setGender(Gender.FEMALE);
memberForm.setEmail("test@naver.com");
memberService.saveUser(memberForm);
Member member = memberRepository.getById(1L);
Assertions.assertThat(member.getUserId()).isEqualTo(userId);
}
...
Postman
테스트 코드 외에도 직접 Postman 으로 API를 호출하여 확인해볼 수 있다.
'Project > Project Process' 카테고리의 다른 글
SpringBoot 재고 감소 동시성 제어 - Redisson 사용 (+ AOP 적용) (0) | 2022.10.28 |
---|---|
[SpringBoot + JPA 프로젝트] (2) Mac Local Mysql 설치 및 프로젝트 DB 연동 (0) | 2021.11.18 |
[SpringBoot + JPA 프로젝트] (1) MainController 통합테스트 진행 (0) | 2021.11.18 |
eureka-server 프로젝트 구성 (0) | 2021.06.05 |