12일차 - 서비스 계층과 트랜잭션
2024. 8. 13. 17:56ㆍ코딩 자율학습 스프링 부트3 자바 백엔드 개발 입문 스터디
서비스와 트랜잭션의 개념
서비스
서비스란 컨트롤러와 레포지토리 사이에 위치하는 계층으로, 서버의 핵심 기능을 처리하는 순서를 총괄한다.
- 클라이언트가 요청을 보낸다.
- 컨트롤러에서 요청을 받아 서비스로 전달한다.
- 서비스에서 코드 흐름을 따라 처리를 진행할 때 필요한 데이터를 레포지토리에서 받는다.
- 레포지토리는 DB에서 필요한 데이터를 가져와 서비스로 반환한다.
트랜잭션
일반적으로 서비스 업무 처리는 트랜잭션 단위로 진행된다. 트랜잭션 이란 모두 성공해야 하는 일련의 과정으로, 쪼갤 수 없는 업무 처리의 최소 단위이다.
서비스 계층 만들기
클라이언트 요청,응답과 레포지토리에서 데이터를 가져오는 역할을 컨트롤러에서 다 진행했는데, 컨트롤러에서는 클라이언트의 요청, 응답 역할, 서비스에서는 레포지토리에서 데이터를 가져오는 역할로 분업하여야 한다.
@Service
public class ArticleService() {
@Autowired
ArticleRepository articleRepository;
}
- service 클래스를 생성하여 @Service 를 추가한다. 해당 어노테이션은 클래스를 서비스로 인식해 스프링 부트에 서비스 객체를 생성한다.
- 서비스에서 레포지토리를 통해 데이터를 받을 수 있게 객체를 주입한다.
모든, 단일 게시글 조회 요청
- Service
- 서비스가 조회 요청을 처리한다.
public List<Article> index() {
return articleRepository.findAll();
}
public Article show(Long id) {
return articleRepository.findById(id).orElse(null);
}
- Controller
- 컨트롤러에서는 요청이 들어오면 서비스로 전달한다.
@GetMapping("/api/articles")
public List<Article> index() { return articleService.index(); }
@GetMapping("/api/articles/{id}")
public Article show(@PathVariable Long id) { return articleService.show(id); }
게시글 생성 요청
- Service
- id 는 데이터를 생성할 때 DB가 자동 생성 해주기 때문에, id 값이 들어가지 않도록 방지한다.
public Article create(ArticleForm dto) {
Article article = dto.toEntity();
if (article.getId() != null) {
return null;
}
return articleRepository.save(article);
}
- Controller
@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
public Article update(Long id, ArticleForm dto) {
Article article = dto.toEntity();
Article target = articleRepository.findById(id).orElse(null);
if (target == null || id != article.getId()) {
return null;
}
target.patch(article);
Article updated = articleRepository.save(target);
return updated;
}
- Controller
@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
public Article delete(Long id, ArticleForm dto) {
Article article = dto.toEntity();
Article target = articleRepository.findById(id).orElse(null);
if (target == null) {
return null;
}
articleRepository.delete(target);
return target;
}
- Controller
@DeleteMapping("/api/articles/{id}")
public ResponseEntity<Article> delete(@PathVariable Long id, @RequestBody ArticleForm dto) {
Article deleted = articleService.delete(id, dto);
return (deleted != null) ? ResponseEntity.status(HttpStatus.NO_CONTENT).build() : ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
트랜잭션 맛보기
/api/transaction-test URL 로 요청받을 수 있는 컨트롤러 메서드를 추가한다. 데이터를 한꺼번에 생성 요청 한 후에 오류를 발생시키고, 롤백되는지 확인해본다.
@PostMapping("/api/transaction-test")
public ResponseEntity<List<Article>> transactionTest(@RequestBody List<AritlcleForm> dtos) {
List<Aritlcles> createdList = articleService.createArticles(dtos);
return (createdList != null) ?
ResponseEntity.status(HttpStatus.OK).body(createdList) :
ResponseEntity.status(HttpStatus.BAD_REQUEST).build() ;
}
public List<Article> createdArticles(List<Article> dtos) {
List<Article> articleList = dtos.stream()
.map(dto -> dto.toEntity)
.collect(Collectors.toList());
articleList.stream()
.forEach(article -> articleRepository.save(article));
articleRepository.findById(-1L)
.orElseThrow(() -> new IllegalArgumentException("결제 실패"));
return articleList;
}
- Service에서 id가 -1인 데이터를 찾게 함으로써 오류를 발생시켰고, 이 상태에서 데이터 생성 요청을 보내면 오류가 발생한다.
- 하지만 데이터 조회를 했을 때 DB 에 저장이 되어있는 상태이다.
- 이렇게 오류가 발생했을 때 데이터가 저장되지 않고 데이터 생성 실패 이전 상황으로 되돌리기 위해서 트랜잭션을 선언하면 된다.
@Transactional
public List<Article> createdArticles(List<Article> dtos) {
List<Article> articleList = dtos.stream()
.map(dto -> dto.toEntity)
.collect(Collectors.toList());
articleList.stream()
.forEach(article -> articleRepository.save(article));
articleRepository.findById(-1L)
.orElseThrow(() -> new IllegalArgumentException("결제 실패"));
return articleList;
}
'코딩 자율학습 스프링 부트3 자바 백엔드 개발 입문 스터디' 카테고리의 다른 글
14일차 - 댓글 CURD - 엔티티, 레포지토리 만들기 (0) | 2024.08.16 |
---|---|
13일차 - 테스트 코드 작성하기 (0) | 2024.08.16 |
11일차 - HTTP와 REST 컨트롤러 (0) | 2024.08.12 |
10일차 - REST API와 JSON (0) | 2024.08.09 |
9일차 - CRUD와 SQL 쿼리 종합 (0) | 2024.08.08 |