스프링 부트, 입문!

스프링 부트, 입문!

쉽고 빠르게 배우는, 스프링 부트 첫걸음!

20 서비스와 트랜잭션(transaction), 그리고 롤백(rollback)

# 서비스와 트랜잭션, 그리고 롤백 ## 미션 서비스 계층을 추가하여, 기존 Article REST API를 리팩터링하시오. ![홍팍-스프링-부트-입문-서비스와-트랜잭션-미션](http://drive.google.com/thumbnail?export=view&sz=w960&id=1e5zPAPqX6TtH2P4AQuQGt4Ltcnxe2FIm) ## 03:39 서비스 생성 - @Service, DI, @Autowired #### ../api/ArticleApiController ``` package com.example.firstproject.api; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @Slf4j @RestController public class ArticleApiController { @Autowired private ArticleService articleService; } ``` #### ../serivce/ArticleService ``` package com.example.firstproject.service; import com.example.firstproject.repository.ArticleRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class ArticleService { @Autowired private ArticleRepository articleRepository; } ``` ## 06:00 리팩토링, Article 목록 조회 #### ../api/ArticleApiController ``` ... @Slf4j @RestController public class ArticleApiController { @Autowired private ArticleService articleService; // GET @GetMapping("/api/articles") public List<Article> index() { return articleService.index(); } } ``` #### ../service/ArticleService ``` package com.example.firstproject.service; import com.example.firstproject.entity.Article; import com.example.firstproject.repository.ArticleRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class ArticleService { @Autowired private ArticleRepository articleRepository; public List<Article> index() { return articleRepository.findAll(); } } ``` ## 07:29 리팩토링, Article 단건(상세) 조회 #### ../api/ArticleApiController ``` ... @Slf4j @RestController public class ArticleApiController { ... @GetMapping("/api/articles/{id}") public Article show(@PathVariable Long id) { return articleService.show(id); } } ``` #### ../service/ArticleService ``` ... @Service public class ArticleService { ... public Article show(Long id) { return articleRepository.findById(id).orElse(null); } } ``` ## 08:44 리팩토링, Article 생성 #### ../api/ArticleApiController ``` ... @Slf4j @RestController public class ArticleApiController { ... // POST @PostMapping("/api/articles") public ResponseEntity<Article> create(@RequestBody ArticleForm dto) { Article created = articleService.create(dto); return (created != null) ? ResponseEntity.status(HttpStatus.OK).body(created) : ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } } ``` #### ../service/ArticleService ``` ... @Service public class ArticleService { ... public Article create(ArticleForm dto) { Article article = dto.toEntity(); if (article.getId() != null) { return null; } return articleRepository.save(article); } } ``` ## 12:41 리팩토링, Article 수정 #### ../api/ArticleApiController ``` ... @Slf4j @RestController public class ArticleApiController { ... // PATCH @PatchMapping("/api/articles/{id}") public ResponseEntity<Article> update(@PathVariable Long id, @RequestBody ArticleForm dto) { Article updated = articleService.update(id, dto); return (updated != null) ? ResponseEntity.status(HttpStatus.OK).body(updated): ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } } ``` #### ../service/ArticleService ``` ... @Slf4j @Service public class ArticleService { ... public Article update(Long id, ArticleForm dto) { // 1: DTO -> 엔티티 Article article = dto.toEntity(); log.info("id: {}, article: {}", id, article.toString()); // 2: 타겟 조회 Article target = articleRepository.findById(id).orElse(null); // 3: 잘못된 요청 처리 if (target == null || id != article.getId()) { // 400, 잘못된 요청 응답! log.info("잘못된 요청! id: {}, article: {}", id, article.toString()); return null; } // 4: 업데이트 target.patch(article); Article updated = articleRepository.save(target); return updated; } } ``` ## 16:22 리팩토링, Article 삭제 #### ../api/ArticleApiController ``` ... @Slf4j @RestController public class ArticleApiController { ... // DELETE @DeleteMapping("/api/articles/{id}") public ResponseEntity<Article> delete(@PathVariable Long id) { Article deleted = articleService.delete(id); return (deleted != null) ? ResponseEntity.status(HttpStatus.NO_CONTENT).build() : ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } } ``` #### ../service/ArticleService ``` ... @Slf4j @Service public class ArticleService { ... public Article delete(Long id) { // 대상 찾기 Article target = articleRepository.findById(id).orElse(null); // 잘못된 요청 처리 if (target == null) { return null; } // 대상 삭제 articleRepository.delete(target); return target; } } ``` ## 18:56 트랜잭션 맛보기, 묶음 Article 생성 - @Transactional #### ../api/ArticleApiController ``` ... @Slf4j @RestController public class ArticleApiController { ... // 트랜잭션 -> 실패 -> 롤백! @PostMapping("/api/transaction-test") public ResponseEntity<List<Article>> transactionTest(@RequestBody List<ArticleForm> dtos) { List<Article> createdList = articleService.createArticles(dtos); return (createdList != null) ? ResponseEntity.status(HttpStatus.OK).body(createdList) : ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } } ``` #### ../service/ArticleService ``` package com.example.firstproject.service; import com.example.firstproject.dto.ArticleForm; import com.example.firstproject.entity.Article; import com.example.firstproject.repository.ArticleRepository; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.stream.Collectors; @Slf4j @Service public class ArticleService { ... @Transactional public List<Article> createArticles(List<ArticleForm> dtos) { // dto 묶음을 entity 묶음으로 변환 List<Article> articleList = dtos.stream() .map(dto -> dto.toEntity()) .collect(Collectors.toList()); // entity 묶음을 DB로 저장 articleList.stream() .forEach(article -> articleRepository.save(article)); // 강제 예외 발생 articleRepository.findById(-1L).orElseThrow( () -> new IllegalArgumentException("결제 실패!") ); // 결과값 반환 return articleList; } } ``` ## 🔥 구글링 훈련하기 - Spring Controller Service Repository 역할 - 트랜잭션과 롤백 - @Service - @Transactional