- 데이터 DB에 저장
- 일반적으로 Controller -> (DTO) -> Service -> (DTO) -> Repository -> (Entity, Domain) -> DB 와 같이 클라이언트의 요청이 들어오고 DB에 데이터가 저장되는 흐름이다.
- Post.java
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PUBLIC)
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@Lob
private String content;
@Builder
public Post(String title, String content) {
this.title = title;
this.content = content;
}
}
- @Lob 어노테이션의 경우 java에서 입력된 String을 DB에 저장할 때 Long text로 변환해서 저장하게 도와주는 어노테이션이다.
- @Builder 어노테이션을 이용하여 Builder 패턴을 사용할 수 있다.
- Builder의 장점
1. 가독성에 좋다.
2. 필요한 값만 받을 수 있다.
3. 객체의 불변성
- 생성자를 이용한 객체 생성 방식은 필드의 수가 많고, 때에 따라 필요한 생성자 파라미터 값이 계속해서 변할 경우 번거롭다. 하지만 builder를 사용하게 되면 1. 원하는 필드만 가져와서 사용할 수 있게 되고, 2. 파라미터의 순서도 신경쓰지 않아도 되며, 3. 어떤 필드에 대한 값인지에 대한 가독성이 좋아진다.
- PostRepository.java
public interface PostRepository extends JpaRepository<Post, Long> {
}
- JPA를 이용한 Repository를 작성한 코드이다. 엔티티 클래스의 타입은 Post이고, 기본값의 타입은 Long이다.
- PostService.java
@Slf4j
@Service
@RequiredArgsConstructor
public class PostService {
private final PostRepository postRepository;
public void write(PostCreate postCreate) {
Post post = Post.builder()
.title(postCreate.getTitle())
.content(postCreate.getContent())
.build();
postRepository.save(post);
}
}
- Service를 통해 Repository의 메서드를 호출해 DB에 데이터를 저장하게 된다.
- DTO에 해당하는 PostCreate 클래스를 통해 데이터를 받아오고, 해당 데이터를 엔티티에 해당하는 Post 객체에 넣어 Repository를 통해 DB에 저장하게 된다.
- Reposiytory를 통해 DB에 저장될 때는 엔티티가 파라미터로 넘어가야되고, Controller, Service, Repositroy에서 데이터를 주고 받을 때는 DTO를 통해 데이터를 주고 받는 것이 좋다.
* 기존의 코드들 builder 패턴으로 변경
-PostCreate.java
@Getter @Setter
@ToString
public class PostCreate {
@NotBlank(message = "title을 입력해주세요")
private String title;
@NotBlank(message = "content를 입력해주세요")
private String content;
@Builder
public PostCreate(String title, String content) {
this.title = title;
this.content = content;
}
}
- ErrorResponse.java
@Getter
public class ErrorResponse {
private final String code;
private final String message;
private final Map<String, String> validation = new HashMap<>();
@Builder
public ErrorResponse(String code, String message) {
this.code = code;
this.message = message;
}
public void addValidation(String field, String errorMessage) {
this.validation.put(field, errorMessage);
}
}
- ExceptionController.java
@Slf4j
@ControllerAdvice
public class ExceptionController {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody // controller는 rest이기 때문에 body로 넘어가는데
// controllerAdvice는 viewResolver로 넘어가기 때문에 body 어노테이션을 달아서
// body로 넘겨준다.
public ErrorResponse exceptionHandler(MethodArgumentNotValidException e) {
ErrorResponse response = ErrorResponse.builder()
.code("400")
.message("잘못된 요청입니다.").build();
for (FieldError fieldError : e.getFieldErrors()) {
response.addValidation(fieldError.getField(), fieldError.getDefaultMessage());
}
return response;
}
}
- Controller에 요청이 들어오게 되면 해당 데이터를 DB에 저장하기 위한 코드로 변경시켜준다.
- PostController.java
@RestController
@Slf4j
@RequiredArgsConstructor
public class PostController {
private final PostService postService;
@PostMapping("/posts")
public void post(@RequestBody @Valid PostCreate request) {
postService.write(request);
}
}
- Test 코드
- PostControllerTest.java
@SpringBootTest
@AutoConfigureMockMvc
class PostControllerTest {
@Autowired
private ObjectMapper objectMapper;
@Autowired
private PostRepository postRepository;
@Autowired
private MockMvc mockMvc;
@BeforeEach
void clean() {
postRepository.deleteAll();
}
@Test
@DisplayName("/posts 요청시 DB에 값이 저장된다.")
void saveToDb() throws Exception {
PostCreate request = PostCreate.builder()
.title("제목입니다.")
.content("내용입니다.")
.build();
String json = objectMapper.writeValueAsString(request);
//expected
mockMvc.perform(MockMvcRequestBuilders.post("/posts")
.contentType(MediaType.APPLICATION_JSON)
.content(json)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());
assertEquals(1L, postRepository.count());
Post post = postRepository.findAll().get(0);
assertEquals("제목입니다.", post.getTitle());
assertEquals("내용입니다.", post.getContent());
}
}
//@WebMvcTest
@SpringBootTest
@AutoConfigureMockMvc
//MockMvc를 주입받기 위해서 사용한다.
- 기존에 @WebMvcTest 어노테이션을 사용했지만 간단한 Controller 요청이 아닌 Service, Repository까지 웹 전체에 대한 테스트를 하기 위해선 @SpringBootTest 어노테이션이 필요하다.
- 또한 @SpringBootTest 어노테이션을 사용하게 되면 MockMvc를 주입 받을 수 없고, @WebMvcTest를 같이 사용할 수 없기 때문에, @AutoConfigureMockMvc 어노테이션을 주입 받음으로서 MockMvc를 주입 받는다.
@Autowired
private ObjectMapper objectMapper;
- ObjectMapper는 Jackson 라이브러리에서 제공하는 클래스로, 자바 객체와 JSON 데이터 간의 변환을 처리하는 데 사용되는 도구이다.
- writeValue() : 자바 객체 -> JSON
- readValue() : JSON -> 자바 객체
.......
PostCreate request = PostCreate.builder()
.title("제목입니다.")
.content("내용입니다.")
.build();
String json = objectMapper.writeValueAsString(request);
//expected
mockMvc.perform(MockMvcRequestBuilders.post("/posts")
.contentType(MediaType.APPLICATION_JSON)
.content(json)
)
.......
- builder를 통해 객체를 생성해주고, MockMvcRequestBuilders의 content에는 String 형식만 들어 갈 수 있기 때문에 writeValueAsString을 이용하여 JSON 형식의 String을 반환해준다.
- 상황 별 Controller 반환 값
- PostController.java
@RestController
@Slf4j
@RequiredArgsConstructor
public class PostController {
private final PostService postService;
@PostMapping("/posts")
public void post(@RequestBody @Valid PostCreate request) {
postService.write(request);
}
}
- Controller를 보게 되면 요청에 대한 응답으로 아무런 데이터를 전송하지 않는 것을 볼 수 있다.
- 상황에 따라 요청에 대해 여러가지 응답을 줄 수 있다.
1. Entity를 response로 응답하기
- PostService.java
@Slf4j
@Service
@RequiredArgsConstructor
public class PostService {
private final PostRepository postRepository;
public Post write(PostCreate postCreate) {
Post post = Post.builder()
.title(postCreate.getTitle())
.content(postCreate.getContent())
.build();
return postRepository.save(post);
}
}
- postRepository의 save() 메서드는 Post 객체를 저장하고 반환하기 때문에 반환값을 Post로 받을 수 있다.
- PostController.java
@RestController
@Slf4j
@RequiredArgsConstructor
public class PostController {
private final PostService postService;
@PostMapping("/posts")
public Post post(@RequestBody @Valid PostCreate request) {
return postService.write(request);
}
}
- 요청에 대한 응답으로 Post 엔티티를 반환하게 되는데, @RestController에 의해 엔티티의 데이터가 JSON 형태로 반환된다.
2. primary_id를 response로 응답하기
- PostService.java
@Slf4j
@Service
@RequiredArgsConstructor
public class PostService {
private final PostRepository postRepository;
public Long write(PostCreate postCreate) {
Post post = Post.builder()
.title(postCreate.getTitle())
.content(postCreate.getContent())
.build();
postRepository.save(post);
return post.getId();
}
}
- PostController.java
@RestController
@Slf4j
@RequiredArgsConstructor
public class PostController {
private final PostService postService;
@PostMapping("/posts")
public Map post(@RequestBody @Valid PostCreate request) {
Long id = postService.write(request);
return Map.of("Id", id);
}
}
- Entity를 응답하는 방식과 같이, Id를 반환 할 수 있다.
'Programming > Spring' 카테고리의 다른 글
Spring - 게시글 조회 (다건 조회, 임시 H2 활용한 실제 서비스 확인) (0) | 2023.08.30 |
---|---|
Spring - 게시글 조회, 클래스 분리 (단건) (0) | 2023.08.29 |
Spring - 데이터 검증(컨트롤러, 데이터 타입, @ControllerAdvice...) (0) | 2023.08.28 |
JPA - 기본 동작 방식, 아키텍처 (0) | 2023.08.01 |
Spring Data JPA - 새로운 엔티티 구별하기 (0) | 2023.07.26 |