# 서비스와 트랜잭션, 그리고 롤백
## 미션
서비스 계층을 추가하여, 기존 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