기존 RestAPI 구현에 서비스 계층을 추가하기.
service는 controller와 repository 사이에 있는 계층으로 업무의 순서를 총괄한다.
기존의 ArticleApiController.java에서 ArticleRepository로 연결되는 부분을 ArticleService의 객체로의 연결로 변경해준다.
@RestController
public class ArticleApiController {
//DI. 생성 객체를 가져와 연결
@Autowired
private ArticleService articleService;
//get
@GetMapping("/api/articles")
public List<Article> index(){
return articleService.index();
}
@GetMapping("/api/articles/{id}")
public Article show(@PathVariable Long id){
return articleService.show(id);
}
//post
@PostMapping("/api/articles")
public ResponseEntity<Article> crete(@RequestBody ArticleForm dto){
Article created = articleService.create(dto);
return (created != null) ?
ResponseEntity.status(HttpStatus.OK).body(created) :
ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
//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();
}
//delete
@DeleteMapping("/api/articles/{id}")
public ResponseEntity<Article> delete(@PathVariable Long id){
Article deleted = articleService.delete(id);
return (deleted!= null)?
ResponseEntity.status(HttpStatus.OK).build():
ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
}
서비스 선언. 서비스 객체를 스프링 부트에 생성해주는 어노테이션.
@Service
public class ArticleService {
@Autowired
private ArticleRepository articleRepository;
public List<Article> index() {
return articleRepository.findAll();
}
public Article show(Long id) {
return articleRepository.findById(id).orElse(null);
}
public Article create(ArticleForm dto) {
Article article = dto.toEntity();
if(article.getId() != null){
return null;
}
return articleRepository.save(article);
}
public Article update(Long id, ArticleForm dto) {
Article article = dto.toEntity();
Article target = articleRepository.findById(id).orElse(null);
if(target == null || id != dto.toEntity().getId()){
return null;
}
target.patch(article);
return articleRepository.save(target);
}
public Article delete(Long id) {
Article target= articleRepository.findById(id).orElse(null);
if(target ==null){
return null;
}
articleRepository.delete(target);
return target;
}
}
트렌젝션 : 한번에 성공해야 하는 일련의 과정
서비스가 트렌젝션을 관리한다.
트렌젝션을 연습해보기 위해서 ResponseEntity<List< Article>>을 통해 여러 데이터를 한번에 요청 받는다.
(예를 들어, id=1/식당 입장 - id=2/메뉴 고민 - id=3 주문완료 )
//ArticleApiController.java
@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();
}
해당 메소드를 트렌젝션으로 묶어줌.
트렌젝션 메소드는 중간에 과정이 실패하면 초기단계로 돌아오는 롤백 기능이 있다.
//ArticleService.java
@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("fail..")
);
return articleList;
}
위의 코드를 for문으로 작성한다면..
id값을 음수로 줘서 일부러 에러를 발생시켰다.
트렌젝션으로 묶어주었기 때문에 롤백 기능 > 에러가 발생했을때 초기단계로 돌아간다.
500 서버 내부 에러
기존 RestAPI 구현에 Service 계층을 추가해줌으로..
Controller는 client한테 "요청받기", "응답처리"만 실행하고,
Service가 "업무처리"와 "업무흐름(트렌젝션)관리" 또한 "업무과정 실패에 대한 처리(롤백)"을 실행한다.