프로젝트

CRUD를 분석해보자 (2) - 게시글 수정/삭제/조회 API

ummchicken 2023. 2. 11. 02:23

1편 게시글 생성에 이어...

 

 

 

✔️ 게시글 수정 / 조회

 

먼저 Controller에 추가하자.

[PostsApiController]

@RequiredArgsConstructor // 1
@RestController
public class PostsApiController {

    private final PostsService postsService;

    // ...
    
    @PutMapping("/api/v1/posts/{id}")
    public Long update(@PathVariable Long id, @RequestBody PostsUpdateRequestDto postsUpdateRequestDto) {
        
        return postsService.update(id, postsUpdateRequestDto);
    }
    
    @GetMapping("/api/v1/posts/{id}")
    public PostsResponseDto findById(@PathVariable Long id) {
        
        return postsService.findById(id);
    }

}

 

 

 

[PostResponseDto] - 회원 정보 View에 보여주는 Dto

@Getter
public class PostsResponseDto {
    
    private Long id;
    private String title;
    private String content;
    private String author;
    
    public PostsResponseDto(Posts entity) {
        this.id = entity.getId();
        this.title = entity.getTitle();
        this.content = entity.getContent();
        this.author = entity.getAuthor();
    }
    
}
  • PostsResponseDto는 Entity의 필드 중 일부만 사용한다. (이 예제에서는 다 씀)
    • 그러므로 생성자로 Entity를 받아 필드에 값을 넣는다.

 

 

 

[PostsUpdateRequestDto] - update할 요소들

@Getter
@NoArgsConstructor
public class PostsUpdateRequestDto {
    
    private String title;
    private String content;
    
    @Builder
    public PostsUpdateRequestDto(String title, String content) {
        this.title = title;
        this.content = content;
    }
    
}

 

 

 

그 다음 Posts에 추가한다.

[Posts.java]

@Getter
@NoArgsConstructor
@Entity
public class Posts {

    // ...
    
    public void update(String title, String content) {
        this.title = title;
        this.content = content;
    }

}

 

 

 

그 다음 

[PostService]에 update와 findById 로직을 추가한다.

@RequiredArgsConstructor
@Service
public class PostsService {

    private final PostsRepository postsRepository;

    // ...

    @Transactional
    public Long update(Long id, PostsUpdateRequestDto postsUpdateRequestDto) {
        Posts posts = postsRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id = " + id));

        posts.update(postsUpdateRequestDto.getTitle(), postsUpdateRequestDto.getContent());

        return id;
    }

    @Transactional
    public PostsResponseDto findById(Long id) {
        Posts entity = postsRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id = " + id));

        return new PostsResponseDto(entity);
    }

}

💡 여기서 신기한 게 있다!

update 기능에서 데이터베이스에 쿼리를 날리는 부분이 없다!

→ 이게 가능한 이유는 JPA의 영속성 컨텍스트 때문이다.

※ 영속성 컨텍스트 : 엔티티를 영구 저장하는 환경.

 

❗ JPA의 핵심 : 엔티티가 영속성 컨텍스트에 포함되어 있냐 아니냐!

JPA의 엔티티 매니저가 활성화된 상태로(Spring Data JPA를 쓴다면 기본 옵션) 

트랜잭션 안에서 데이터베이스에서 데이터를 가져오면

이 데이터는 영속성 컨텍스트가 유지된 상태이다.

이 상태에서 해당 데이터의 값을 변경하면, 트랜잭션이 끝나는 시점에 해당 테이블에 변경분을 반영한다!

즉, Entity 객체의 값만 변경하면, Update 쿼리를 날릴 필요가 없다는 것!!

이 개념을 더티 체킹(dirty checking)이라고 한다.

 

 

 

 

그럼 Update 쿼리를 수행하는지 테스트코드로 확인해보자.

[PostsApiControllerTest]

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class PostsApiControllerTest {

    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private PostsRepository postsRepository;

    @AfterEach
    public void tearDown() throws Exception {
        postsRepository.deleteAll();
    }

    // ...

    @Test
    public void Posts_수정된다() throws Exception {
        // given
        Posts savePosts = postsRepository.save(Posts.builder()
                .title("title")
                .content("content")
                .author("ummchicken")
                .build());

        Long updateId = savePosts.getId();
        String expectedTitle = "title2";
        String expectedContent = "content2";

        PostsUpdateRequestDto postsUpdateRequestDto = PostsUpdateRequestDto.builder()
                .title(expectedTitle)
                .content(expectedContent)
                .build();

        String url = "http://localhost:" + port + "/api/v1/posts/" + updateId;

        HttpEntity<PostsUpdateRequestDto> postsUpdateRequestDtoHttpEntity = new HttpEntity<>(postsUpdateRequestDto);

        // when
        ResponseEntity<Long> responseEntity = restTemplate.exchange(url, HttpMethod.PUT, postsUpdateRequestDtoHttpEntity, Long.class);

        // then
        assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(responseEntity.getBody()).isGreaterThan(0L);

        List<Posts> all = postsRepository.findAll();
        assertThat(all.get(0).getTitle()).isEqualTo(expectedTitle);
        assertThat(all.get(0).getContent()).isEqualTo(expectedContent);
    }

}

 

 

통과한다.

 

 

❓ 그럼 실제로 실행된 쿼리는 뭘까?

→ 먼저 save(savePosts)를 한 다음

 

→ update 쿼리가 나갔다!

 

 

 

💡 Postman으로 조회 API 보내보기

 

먼저

→ 게시글 정보를 저장한다.

 

 

❓ 그럼 실제로 실행된 쿼리는 뭘까?

insert 쿼리 나가고

 

 

→ DB에도 정상 insert 된다.

 

 

 

그리고

1. 브라우저로 API 조회 : http://localhost:8080/api/v1/posts/1

하던가, 

 

 

2. Postman으로 

보내던가. (GET)

 

 

결과는 동일하게 나온다.

 

그리고 동일한 

→ select 쿼리가 나간다.

(위가 브라우저, 밑이 Postman)

 

 

 

💡 Postman으로 수정 API 보내보기

 

먼저 

→ 아까 저장된 1번 게시글의 정보를 PUT으로 변경한다.

 

 

❓ 그럼 실제로 실행된 쿼리는 뭘까?

→ update 쿼리가 나간다.

 

 

 

브라우저에 확인해 보거나

 

 

Postman으로 조회하거나 (GET)

 

결과는 동일하고, 

 

 

DB도 정상 update(변경) 되었다!

 

 

🚨 아 여기서 내가 놓친 부분이 있다.

PostsUpdateRequestDto에 title이랑 content만 변경 가능하게 해놔서 

author를 ummchicken에서 ummchicken Pizza로 변경했음에도 update되지 않았다.

 

 

 


 

✔️ 게시글 삭제

 

먼저 [PostsService]에 추가한다.

@RequiredArgsConstructor
@Service
public class PostsService {

    private final PostsRepository postsRepository;

    // ...

    @Transactional
    public void delete (Long id) {
        Posts posts = postsRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id = " + id));

        postsRepository.delete(posts);
    }

}

 

 

 

그리고 [PostsApiController]에 추가한다.

@RequiredArgsConstructor // 1
@RestController
public class PostsApiController {

    private final PostsService postsService;

    // ...

    @DeleteMapping("/api/v1/posts/{id}")
    public Long delete(@PathVariable Long id) {
        postsService.delete(id);

        return id;
    }

}

 

 

 

난 viewPage를 만들지 않았으므로, Postman으로 조회해보겠다.

 

일단 위에서 했던 것처럼 회원 정보를 하나 생성해주고, 

→ 삭제된 아이디 1을 반환한다.

 

 

delete 쿼리 나가고 

 

 

→ DB도 정상 삭제 되었다.

 

 

 

 

 

- CRUD API 훑어보기 끝 -