[작성글 저장] 게시글 저장 구현
게시글 저장 구현
이제부터 게시판의 가장 기본적인 기능인 CRUD 의 시작이다. 우선 게시글 저장부터 구현해보자.
JpaRepository 인터페이스를 사용하여 자동 주입되는 JpaRepository 구현체의 단건 저장 기능을 사용할것이다.
컨트롤러에서 바로 Repository 를 호출해도 되지만 PostService 를 만들어서 서비스 계층에서 호출하는게 바람직하다.
가장 간단한 방법으로 게시글 저장을 구현하고 테스트까지 해보자.
컨트롤러에서 클라이언트로 어떻게 응답을 내려줄것인가?
[클라이언트에서 요구하는 응답 케이스]
- 저장한 데이터 Entity 를 response 로 반환한다.
- 저장한 데이터의 primary_id 를 response 로 반환한다.
-> Client 에서는 수신한 id 로 글 조회 API 를 통해서 데이터를 수신받음 - 응답 필요 없다.
-> 클라이언트에서 모든 글 데이터 context 를 잘 관리함
이처럼 클라이언트 개발자마다 실력도 스타일도 다 다르기 때문에 요구하는 응답값이 다 다르다.
따라서 서버에서 반드시 이렇게 할겁니다 !! 라고 하는건 안좋다. 서버에서 유연하게 대응하는게 좋다.
한 번에 일괄적으로 잘 처리되는 케이스는 없다. 결국 잘 관리되는 형태가 중요하기 때문에 코드를 잘 짜야한다.
PostController
요청 데이터를 PostCreate 객체로 받아 서비스로 넘겨주고 등록한 Post 정보를 반환한다.
엔티티 자체를 응답으로 보내주는건 좋지 않지만 우선은 간략하게 구현하겠다.
@RestController
@RequiredArgsConstructor
public class PostController {
private final PostService postService;
@PostMapping("/posts")
public Post post(@RequestBody @Valid PostCreate request) {
return postService.write(request);
}
}
PostCreate
@Getter
@ToString
@NoArgsConstructor
public class PostCreate {
@NotBlank(message = "제목을 입력해주세요.")
private String title;
@NotBlank(message = "내용을 입력해주세요.")
private String content;
public PostCreate(String title, String content) {
this.title = title;
this.content = content;
}
}
ㄴ @NoArgsConstructor
기본 생성자를 만들어주는 Lombok 애노테이션이다.
Build설정이 Gradle 로 되어있으면 기본 생성자 없이도 데이터를 객체에 매핑시켜주는 역직렬화가 잘 되지만
Build설정이 IntelliJ 로 되어 있으면 기본 생성자 없이는 역직렬화가 안된다.
PostService
컨트롤러에서 받은 요청데이터를 바탕으로 Post 객체를 만들어 Repository 로 넘겨준다.
그 후 컨트롤러에 저장한 Post 정보를 반환한다.
@Service
@RequiredArgsConstructor
public class PostService {
private final PostRepository postRepository;
public Post write(PostCreate request) {
Post post = new Post(request.getTitle(), request.getContent());
return postRepository.save(post);
}
}
Post
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@Lob
private String content;
public Post(String title, String content) {
this.title = title;
this.content = content;
}
}
ㄴ @NoArgsConstructor
기본 생성자를 만들어주는 Lombok 애노테이션이다.
JPA 에서 엔티티는 기본 생성자가 있어야 DB 와 매핑을 할 수 있기 때문에 필요하다.
다른 곳에서 남용되는것을 막기 위해 제한자 설정을 AccesLevel.PROTECTED 로 한다.
PostRepository
JpaRepository 인터페이스에 의존성 주입된 JpaRepository 구현체에 구현돼있는 save() 메서드를 사용하여
DB에 Post 를 저장한다.
public interface PostRepository extends JpaRepository<Post, Long> {
}
PostServiceTest
글 작성 시 DB에 Post 를 저장하고 제대로 저장됐는지 확인하는 테스트이다.
@SpringBootTest
class PostServiceTest {
@Autowired
private PostService postService;
@Autowired
private PostRepository postRepository;
@BeforeEach
void clean() {
postRepository.deleteAll();
}
@DisplayName("글 작성")
@Test
void test() {
// given
PostCreate postCreate = PostCreate.builder()
.title("글 제목입니다")
.content("글 내용입니다 하하")
.build();
// when
postService.write(postCreate);
Post post = postRepository.findAll().get(0);
// then
assertEquals(1L, postRepository.count());
assertEquals("글 제목입니다", post.getTitle());
assertEquals("글 내용입니다 하하", post.getContent());
}
}
테스트를 수행하면 테스트가 성공함을 확인할 수 있다.
PostControllerTest
처음 테스트는 컨트롤러에서 응답이 변경됐기 때문에 글 작성 후 제대로 응답을 내려주는지 확인하는 테스트이다.
2번째 테스트는 DB에 Post 를 저장하고 제대로 저장됐는지 확인하는 테스트이다.
@SpringBootTest
@AutoConfigureMockMvc
class PostControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private PostRepository postRepository;
@BeforeEach
void clean() {
postRepository.deleteAll();
}
@DisplayName("/posts 요청 시 저장한 Post 를 출력한다.")
@Test
void post1() throws Exception {
// when
mockMvc.perform(
MockMvcRequestBuilders.post("/posts")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"title\" : \"글 제목입니다\", \"content\" : \"글 내용입니다 하하\"}")
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.title").value("글 제목입니다"))
.andExpect(jsonPath("$.content").value("글 내용입니다 하하"))
.andDo(print());
}
@DisplayName("/posts 요청 시 DB에 값이 저장된다.")
@Test
void post3() throws Exception {
// when
mockMvc.perform(
MockMvcRequestBuilders.post("/posts")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"title\" : \"글 제목입니다\", \"content\" : \"글 내용입니다 하하\"}")
)
.andExpect(status().isOk())
.andDo(print());
Post post = postRepository.findAll().get(0);
// then
assertEquals(1L, postRepository.count());
assertEquals("글 제목입니다", post.getTitle());
assertEquals("글 내용입니다 하하", post.getContent());
}
}
ㄴ @SpringBootTest
기존의 @WebMvcTest 는 프레젠테이션 계층과 관련된 빈들만 의존성 주입을 해준다.
테스트를 진행하기 위해서는 Repository 호출이 필요하기 때문에 해당 애노테이션을 사용하였다.
ㄴ @AutoConfigureMockMvc
@SpringBootTest 에는 MockMvc 에 의존성을 주입해주는 기능이 없다.
따라서 테스트를 진행하기 위해 MockMvc 의존성 주입이 가능한 해당 애노테이션을 사용하였다.
ㄴ @BeforeEach
모든 테스트 간의 초기 조건은 항상 동일하고 순서가 달라져도 결과는 항상 같아야하기 때문에
각각의 테스트가 시작하기 전에 Repository 의 데이터를 모두 삭제해야한다.
해당 애노테이션을 사용하면 각각의 테스트를 시작하기 전에 지정해둔 메서드를 실행할 수 있다.
테스트를 수행하면 테스트가 성공함을 확인할 수 있다.