JPA 게시판 RESTFul API(2)

shinhyocheol·2021년 7월 15일
0

JPA

목록 보기
2/2

이전글에서는 조회 API를 만들어보았으니 이제 남은 건 상세조회, 등록, 수정, 삭제등등이 있다.

이것들 또한 JpaRepository에서 제공해주는 메서드를 이용해 기능을 만들어보자.

글을 등록 또는 수정하는 과정은 요청자에게 받아야 할 데이터가 있으므로 DTO를 추가로 생성해준다.

RegistPostsDto(등록)

@Getter
@Setter
@ToString
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor
public class RegistPostsDto {

  @NotBlank(message = "'author' is a required input value")
  private String author;

  @NotBlank(message = "'title' is a required input value")
  private String title;

  @NotBlank(message = "'content' is a required input value")
  private String content;

  public Posts toEntity() {
  
    Posts build = Posts.builder()
		.author(author)
		.title(title)
		.content(content)
		.build();
        
    return build;
  }

}

ModifyPostsDto(수정)

@Getter
@Setter
@ToString
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor
public class ModifyPostsDto {

    @Min(1)
    private Long id;

    @NotBlank(message = "'author' is a required input value")
    private String author;

    @NotBlank(message = "'title' is a required input value")
    private String title;

    @NotBlank(message = "'content' is a required input value")
    private String content;

    public Posts toEntity() {
        Posts build = Posts.builder()
                .id(id)
                .author(author)
                .title(title)
                .content(content)
                .build();
        return build;
    }

}

그리고 컨트롤러와 서비스에 기능을 추가한다.

PostsService

@Service("postsService")
@AllArgsConstructor
public class PostsService {
	
  private PostsRepository postsRepository;

  private CustomModelMapper customModelMapper

  // 글 목록 조회
  public List<PostsResDto> getPostsService() {
      
    List<Posts> entityList = postsRepository.findAll();

    return entityList.stream()
     		.map(entity -> customModelMapper.toDto(entity, PostsResDto.class))
             	.collect(Collectors.toList());
  }
	
  // 글 등록
  public Long regPostsService(RegistPostsDto regPosts) {

      Long insertId = postsRepository.save(regPosts.toEntity()).getId();

      return insertId;
  }

  // 글 상세 조회
  public PostsResDto getPostsByIdService(Long id) {
    Posts entity = postsRepository.findById(id)
            .orElseThrow(() -> new ApiOtherException("Posts Result Not Found"));
  	
    return customModelMapper.toDto(entity, PostsResDto.class);
  }
	
  // 글 수정
  public void setPostsService(ModifyPostsDto setPosts) {
     postsRepository.save(setPosts.toEntity());
  }
	
  // 글 삭제
  public void delPostsService(Long id) {
     postsRepository.deleteById(id);
  }

}

위 서비스에서 사용된 JpaRepository의 메서드를 살펴보자.

save() : 등록과 수정 두 작업에 사용된다.

  • id가 없다면 Transient 상태를 인지하고, persist() 메소드를 사용한다.

  • id가 있다면 Detached 상태를 인지하고, merge() 메소드를 사용한다.

delete() : 삭제 메소드이며 나는 ById(이것또한 제공됨)가 붙어있는데 id에 해당되는 레코드 삭제를 실행한다.

PostsController

@RestController
@RequestMapping(value = {"/posts"}, produces = MediaType.APPLICATION_JSON_VALUE)
@AllArgsConstructor
@CrossOrigin(origins = "*")
public class PostsController {
  
  private PostsService postsService;
	
  /**
    * @method 설명 : 게시글 목록조회
    * @param regPosts
    * @throws Exception
    */
    @GetMapping(value = {""})
    public ResponseEntity<List<PostsResDto>> getPosts() {
	return ResponseEntity.ok()
    	.body(postsService.getPostsService());
    }

  /**
    * @method 설명 : 게시글 등록
    * @param regPosts
    * @throws Exception
    */
    @PostMapping(value = {""})
    public ResponseEntity<Long> regPosts(@Valid @RequestBody RegistPostsDto regPosts) throws Exception {
	return ResponseEntity.ok()
    			.body(postsService.regPostsService(regPosts));
    }
	
  /**
    * @method 설명 : 게시글 상세조회
    * @param id
    * @return
    */
    @GetMapping(value = {"/{id}"})
    public ResponseEntity<PostsResDto> getPostsDetail(@PathVariable Long id) {
	return ResponseEntity.ok()
    			.body(postsService.getPostsByIdService(id));
    }
	
  /**
    * @method 설명 : 게시글 수정
    * @param id
    * @param setPosts
    * @throws Exception
    */
    @PutMapping(value = {"/{id}"})
    public ResponseEntity<String> setPosts(
	@PathVariable Long id,
	@Valid @RequestBody PostsSetDto setPosts) throws Exception {

	postsService.setPostsService(setPosts);
    
	return ResponseEntity.ok()
    			.body("UPDATE SUCCESS");
    }
	
  /**
    * @method 설명 : 게시글 삭제
    * @param id
    * @throws Exception
    */
    @DeleteMapping(value = {"/{id}"})
    public ResponseEntity<String> delPosts(
	@PathVariable Long id) throws Exception {

	postsService.delPostsService(id);

	return ResponseEntity.ok()
			.body("DELETE SUCCESS");
    }
	
}

CRUD의 기능이 완성된 서비스와 그에대한 요청을 받는 컨트롤러의 모습이다.

@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@AutoConfigureMockMvc
class SpringJpaApplicationTests {

  private MockMvc mockMvc;

  @Autowired
  private ObjectMapper objectMapper;

  @Autowired
  private WebApplicationContext context;

  @BeforeAll
  public void setUp() {
    this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
 	.addFilters(new CharacterEncodingFilter("UTF-8", true))
	.alwaysDo(print())
	.build();
  }

  @Test
  void contextLoads_게시판_글_목록조회() throws Exception{

    mockMvc.perform(get("/posts"))
	   .andDo(print())
	   .andExpect(status().isOk());
  }

  @Test
  void contextLoads_게시판_글_상세조회() throws Exception {

    mockMvc.perform(get("/posts/1"))
	   .andDo(print())
	   .andExpect(status().isOk());
  }

  @Test
  void contextLoads_게시판_글_등록() throws Exception {

    PostsRegDto posts = new PostsRegDto("작성자", "테스트 제목", "테스트 본문");
    String content = objectMapper.writeValueAsString(posts);

      mockMvc.perform(post("/posts")
	     .content(content)
	     .contentType(MediaType.APPLICATION_JSON)
	     .accept(MediaType.APPLICATION_JSON))
	     .andDo(print())
	     .andExpect(status().isOk());
  }

  @Test
  void contextLoads_게시판_글_수정() throws Exception {
		
      PostsSetDto posts = new PostsSetDto((long)1, "수정자", "제목 수정 테스트", "본문 수정 테스트");
      String content = objectMapper.writeValueAsString(posts);
		
      mockMvc.perform(put("/posts/1")
	     .content(content)
	     .contentType(MediaType.APPLICATION_JSON)
	     .accept(MediaType.APPLICATION_JSON))
	     .andDo(print())
	     .andExpect(status().isOk())
  }


   @Test
   void contextLoads_게시판_글_삭제() throws Exception {

      mockMvc.perform(delete("/posts/1"))
	    .andDo(print())
    	    .andExpect(status().isOk());
   }
	
}

하나씩 차례차례 실행해보며 테스트를 진행해보자.

그리고 예외처리하는 부분이 없어서 예외처리에 대해 의문일 수 있겠는데 나는 예외처리를 공통으로

처리하는 것을 기본적으로 세팅해두고 시작하기때문에 기본적인 예외처리는 모두 되어있다.

@ControllerAdvice 와@ExceptionHandler

예전에 작성한거지만 응답 데이터 구조의 차이만 있을뿐 큰차이는 없으므로 해당 글을 참고하길 바란다.

추가로 본인은 Spring Security를 사용자 검증 서비스에 사용하기 위해 디펜던시를 추가한것이다.

Spring Security를 사용하지 않는다면 그냥 빼버리면 된다.

profile
놀고싶다

0개의 댓글