본문 바로가기
웹개발/SpringBoot

Service와 Transaction

by 철없는민물장어 2023. 3. 11.
728x90
반응형

서비스는 Controller와 Repository 사이에서 동작한다.

 

서비스를 추가함으로써

-컨트롤러는 클라이언트로부터 요청을 받고 응답처리만 하고,
-서비스는 컨트롤러로부터 전달받은 요청을 처리한다.

-필요한 데이터는 리파지토리를 이용해 가져올 수 있다.

 

서비스는 비즈니스 로직을 처리하고, 처리결과를 컨트롤러로 보낸다. @Service 어노테이션을 붙여 사용할 수 있다.

 

 Post요청을 처리하는 과정을 예시로 적어보았다.

1. 컨트롤러에서 Post요청을 받고, 서비스 create(data)를 호출

2. 서비스는 적절하게 데이터를 가공하고 요청에 맞는 비즈니스 로직을 처리함. DB접근이 필요하다면 리파지토리를 호출

3. 리파지토리는 DB에서 데이터를 빼거나 저장하는 역할을 수행

4. 서비스에서 처리결과를 컨트롤러로 전달

5. 컨트롤러에서 처리결과에 맞게 클라이언트에게 응답을 보냄


 

ArticleApiController의 Post처리 내용이다.

서비스를 사용할 것이기 때문에

컨트롤러는 요청을 받고, 응답을 리턴하는 동작만 수행한다.

 

컨트롤러가 Post요청을 받았을 때 호출하는 서비스 create()이다.

여기서 데이터를 가공(dto를 엔티티로 변환하는 등)하고

문제상황도 처리하고

리파지토리를 불러 DB에 값을 저장하도록하기도 한다.

 


더보기
package com.example.firstproject.api;

import com.example.firstproject.dto.ArticleForm;
import com.example.firstproject.entity.Article;
import com.example.firstproject.repository.ArticleRepository;
import com.example.firstproject.service.ArticleService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Slf4j //로깅 사용을위해
@RestController //REST API용 컨트롤러. 데이터(JSON)반환
public class ArticleApiController {
    @Autowired//생성 객체를 가져와 연결
    private ArticleService articleService;
//    //GET
    @GetMapping("/api/articles")
    public List<Article> index(){
        return articleService.index();
    }

    @GetMapping("/api/articles/{id}")
    public Article index(@PathVariable Long id){
        return articleService.show(id);
    }
//    //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();
    }
//    //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).body(deleted):
                ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
    }

    @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();
    }
}

 

컨트롤러.

 

더보기
package com.example.firstproject.service;

import com.example.firstproject.dto.ArticleForm;
import com.example.firstproject.entity.Article;
import com.example.firstproject.repository.ArticleRepository;
import jakarta.transaction.Transactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@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){//새 글 저장인데 id를 적어보내는 경우
            return null;
        }
        return articleRepository.save(article);
    }

    public Article update(Long id, ArticleForm dto){
        //1. 수정용 엔티티 생성
        Article article = dto.toEntity();
        //2. 대상 엔티티 조회
        Article target = articleRepository.findById(id).orElse(null);

        //3. 잘못된 요청 처리
        if(target == null || article.getId() != id){
            return null;
        }
        //4. 업데이트 및 정상응답
        target.patch(article);
        Article updated = articleRepository.save(article);
        return updated;
    }

    public Article delete(Long id){
        Article target = articleRepository.findById(id).orElse(null);

        if(target==null){
            return null;
        }
        //삭제
        articleRepository.delete(target);
        return target;

    }

    //@Transactional //해당 메소드를 트랜잭션으로 묶음
    public List<Article> createArticles(List<ArticleForm> dtos) {
        //dto 묶음을 entity묶음으로 변환
        List<Article> articleList = dtos.stream()
                .map(dto -> dto.toEntity())
                .collect(Collectors.toList());

        log.info(articleList.toString());
        //entity묶음을 DB로 저장
        articleList.stream()
                .forEach(article -> articleRepository.save(article));

        //강제예외 발생시켜봄
        articleRepository.findById(-1L).orElseThrow(
            ()->new IllegalArgumentException("결재 실패")
        );

        //결과값 반환
        return articleList;
    }
}

서비스.

 

 

 

 


 

추가적으로, 서비스에서 로직을 작성할 때, 한 번에 수행되어야 하는 작업들은 트랜잭션으로 묶을 수 있다.

트랜잭션 안의 여러 작업들은 모두 성공하거나, 모두 실패하는 경우만 존재해야 한다.

(예를들어, 1,2,3의 작업이 있을 때 1,2,3 모두 성공하거나 1,2,3 모두 실패하는것은 되나 1,2,만 성공하고 3은 실패하는 등은 안 된다)

transaction은 @Transactional 어노테이션을 붙여서 사용할 수 있다.

 

예시로,

/api/transaction-test 경로로 여러 데이터(List형태)를 보냈을 때

모든 데이터를 다 DB에 넣는 동작을 transaction으로 작성해보자.

    @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();
    }

Controller에서는 List형 dto를 받고, 서비스를 호출한다.

@Transactional //해당 메소드를 트랜잭션으로 묶음
    public List<Article> createArticles(List<ArticleForm> dtos) {
        //dto 묶음을 entity묶음으로 변환
        List<Article> articleList = dtos.stream()
                .map(dto -> dto.toEntity())
                .collect(Collectors.toList());

        log.info(articleList.toString());
        //entity묶음을 DB로 저장
        articleList.stream()
                .forEach(article -> articleRepository.save(article));

        //강제예외 발생시켜봄
        articleRepository.findById(-1L).orElseThrow(
            ()->new IllegalArgumentException("결재 실패")
        );

        //결과값 반환
        return articleList;
    }

서비스에서 @Transactional 어노테이션을 붙여 동작을 트랜잭션으로 묶었다.

(내용은, dto리스트를 엔티티리스트로 변환하고 db에 저장한다. 여기서 db에 잘못된데이터에 접근하여 오류를 발생시킨다.)

여기서 오류가 발생하면, 트랜잭션으로 묶여있기 때문에

앞에서 db에 데이터를 저장했더라도

롤백되어 초기상태로 돌아간다.

728x90
반응형

댓글