// domain - Article.java
@Entity // 엔티티로 지정
@Getter
@NoArgsConstructor
public class Article{
~ 생략 ~
public void update(String title, String content) {
this.title = title;
this.content = content;
}
}
// dto - UpdateArticleRequest
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class UpdateArticleRequest {
private String title;
private String content;
}
@RequiredArgsConstructor
@Service
public class BlogService {
@Transactional
public Article update(long id, UpdateArticleRequest request) {
Article article = blogRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("not found : " + id));
article.update(request.getTitle(), request.getContent());
return article;
}
}
@Transactional
데이터베이스 트랜잭션을 처리하기 위해 Spring에서 제공하는 기능 중 하나이고, 매칭한 메서드를 하나의 트랜잭션으로 묶는 역할을 한다. (ex. 다수의 쿼리를 하나의 트랜잭션으로 묶어 처리하는 경우)
Spring이 해당 메서드 또는 클래스 내의 모든 데이터베이스 작업을 하나의 트랜잭션으로 처리하여, 내부 로직 처리 중에 에러가 발생해도 제대로된 값의 수정을 보장 해준다.
- Transaction
트랜잭션이란, 데이터베이스의 데이터를 바뀌기 위해 묶은 작업의 단위이다
트랜잭션은 안전성을 보장하기 위해 필요한 4가지 성질이 존재한다.
- 원자성(Atomicity)
트랜잭션이 실행될 때, 데이터베이스에 모두 반영되던가, 모두 반영되지 않아야 한다.
- 일관성(Consistency)
트랜잭션의 작업 처리 결과는 항상 일관성이 있어야 한다.
- 독립성(Isolation)
둘 이상의 트랜잭션이 동시에 실행될 때, 다른 트랜잭션의 연산에 끼어들 수 없다.
- 지속성(Durability)
트랜잭션이 성공적으로 완료되었을 경우, 결과는 영구적으로 반영이 되어야 한다.
- 트랜잭션 사용 이유
예를 들어, 계좌 이체를 할 때의 과정을 생각해보면, A 계좌에서 출금, B 계좌에서 입금 이 두 단계로 이루어진다.
만약, 이 과정 중 A 계좌에서 출금만 이루어지고 B 계좌에 입금이 안된다면 심각한 상황이 발생하게 되니, 출금과 입금을 하나의 작업단위, 즉 트랜잭션으로 묶어 실행해줘야 한다는 것이다.
이렇듯 한번에 이루어져야 하는 일련의 작업들을 하나의 단위로 묶어줘야 할 때 트랜잭션을 사용한다.
// controller - BlogApiController
@RequiredArgsConstructor
@RestController
public class BlogApiController {
~ 생략 ~
@PutMapping("/api/articles/{id}")
public ResponseEntity<Article> updateArticle(@PathVariable long id,
@RequestBody UpdateArticleRequest request) {
Article updatedArticle = blogService.update(id, request);
return ResponseEntity.ok()
.body(updatedArticle);
}
}
UpdateArticleRequest(dto) -> BlogApiController(controller)-> BlogService(service) -> Article(domain)
Given
: 블로그 글을 저장하고, 블로그 글 수정에 필요한 요청 객체를 만든다.
When
: UPDATE API로 수정 요청을 보낸다. 이때 요청 탕비은 JSON이며, given 절에서 미리 만들어둔 객체를 요청 본문으로 함께 보낸다.
Then
: 응답코드가 200 OK인지 확인 후 블로그 글 id로 조회한 후에 값이 수정되었는지 확인한다.
// test - BlogApiControllerTest.java
@SpringBootTest // 테스트용 애플리케이션 컨텍스트
@AutoConfigureMockMvc // MockMvc 생성
class BlogApiControllerTest {
~ 생략 ~
@DisplayName("updateArticle: 블로그 글 수정에 성공한다.")
@Test
public void updateArticle() throws Exception {
// given
final String url = "/api/articles/{id}";
final String title = "title";
final String content = "content";
Article savedArticle = blogRepository.save(Article.builder()
.title(title)
.content(content)
.build());
final String newTitle = "new title";
final String newContent = "new content";
UpdateArticleRequest request = new UpdateArticleRequest(newTitle, newContent);
// when
ResultActions result = mockMvc.perform(put(url, savedArticle.getId())
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(request)));
// then
result.andExpect(status().isOk());
Article article = blogRepository.findById(savedArticle.getId()).get();
assertThat(article.getTitle()).isEqualTo(newTitle);
assertThat(article.getContent()).isEqualTo(newContent);
}
}
objectMapper.writeValueAsString()
Jackson ObjectMapper를 사용하여 Java 객체를 JSON 문자열로 변환하는 메서드이다.
Java 객체를 JSON 문자열로 변환하는 이유는 HTTP 통신에는 JSON 형식이 사용되기 때문이다.
추가 설명은 '4.3 테스트 코드' 참고