길벗 코딩자율학습단 12일차

donghan378·2025년 1월 26일

12일차

~12.3 트랜잭션 맛보기(종이책 p.333~362)

12장 서비스 계층과 트랜잭션

12.1 서비스와 트랜잭션의 개념

서비스(service)란 컨트롤러와 리파지터리 사이에 위치하는 계층으로, 서버의 핵심 기능(비즈니스 로직)을 처리하는 순서를 총괄한다.

일반적으로 서비스 업무 처리는 트랜잭션 단위로 진행된다. 트랜잭션(transaction)이란 모두 성공해야 하는 일련의 과정을 뜻하며, 쪼갤 수 없는 업무 처리의 최소 단위이다.

트랜잭션이 실패로 돌아갈 경우 진행 초기 단계로 돌리는 것을 롤백(rollback)이라고 한다.

12.2 서비스 계층 만들기

REST 컨트롤러(api/ArticleApiController.java) 코드를 연다. 이 컨트롤러는 리파지터리와 협업해 게시판의 데이터를 CRUD했는데, 여기에 서비스를 계층을 추가해서 컨트롤러, 서비스, 리파지터리의 역할로 분업한다.

  1. 먼저 ArticleApiController 내 모든 코드를 주석 처리한다. (전체 드래그 후 cmd + /)

  2. 객체 주입된 리파지터리를 서비스로 바꾼다. 맨 위에 두 줄을 블록 설정한 후 cmd + / 을 눌러 주석을 해제하고 다음과 같이 수정한다.

    
    @Autowired
    private ArticleService articleService; // 서비스 객체 주입
    
  3. 아직 ArticleService 클래스를 정의하지 않았으므로 기본 패키지(com.example.firstproject)에 service 라는 패키지를 만들고 그 안에 ArticleService 라는 클래스를 만든다.

  4. 자동 생성된 ArticleService 코드에 @Service 어노테이션을 붙인다. @Service 어노테이션은 해당 클래스를 서비스로 인식해 스프링 부트에 서비스 객체를 생성한다.

  5. 리파지터리와 협업할 수 있도록 articleRepository 필드를 추가하고 @Autowired를 통해 객체 주입한다.

    이제 REST 컨트롤러로 돌아가서 주석을 하나씩 풀며 리팩터링 해본다.

게시글 조회 요청 개선하기

  • 모든 게시글 조회 요청 개선하기
    1. index() 메서드의 주석을 푼다.

    2. 리파지터리를 통해서 데이터를 가져오던 것을 서비스를 통해서 가져온다.

      return 문의 articleRepository.findAll()을 삭제하고 articleService.index() 메서드를 호출한다.

    3. index() 메서드는 아직 만들어지지 않았으므로 빨간색으로 표시된 index() 메서드 위에 마우스를 올리고 Create method ‘index’ in ‘ArticleService’ 를 클릭해서 메서드를 만든다.

    4. return articleRepository.findAll(); 문을 작성해 DB에서 조회한 결과를 반환한다.

    5. Talend API Tester를 통해서 잘 동작하는 것을 확인할 수 있다.

  • 단일 게시글 조회 요청 개선하기
    1. show() 메서드의 주석을 푼다.
    2. return 문의 articleRepository.findById(id).orElse(null)을 삭제하고 articleService.show(id)를 호출한다.
    3. show() 메서드를 작성하지 않았으므로 메서드를 작성하고 return articleRepository.findById(id).orElse(null); 로 작성한다.

게시글 생성 요청 개선하기

  1. create() 메서드의 주석을 푼다.

  2. dto.toEntity()를 삭제하고 articleService.create()를 호출한다. 괄호 안에 dto를 넣어서 전달한다.

  3. 이름이 헷갈리므로 article에서 마우스 오른쪽 버튼을 누르고 Refactor → Rename을 클릭하고 created로 수정한다. 이렇게 하면 article이 사용된 모든 곳이 created로 바뀐다.

  4. 삼항 연산자를 사용하여 return 문에서 결과로 받은 created 가 null이 아니면 good 요청을 보내고 그렇지 않으면 bad 요청을 보내도록 한다.

  5. POST 요청에 대한 응답으로 ResponseEntity에 응답을 실어 보내주기 위해 반환형을 ResponseEntity<Article>로 수정한다.

  6. 서비스에 create() 메서드를 추가한다.

    1. dto를 엔티티로 변환해 article 객체에 저장한다.
    2. 리파지터리가 article을 DB에 저장하도록 한다.
  7. 기존의 있는 데이터로 생성 요청을 보내면 안되기 때문에 article 객체에 id가 존재한다면(null이 아니라면) null을 반환하는 코드를 추가한다.

    
    public Article create(ArticleForm dto) {
    	Article article = dto.toEntity();
    	if (article.getId() != null) {
    		return null;
    	}
    	return articleRepository.save(article);
    }
    

게시글 수정 요청 개선하기

  1. update() 메서드의 주석을 모두 해제한다.

  2. 그리고 모든 코드를 잘라내 메모장에 붙여 넣는다.

  3. 서비스의 update() 메서드를 호출해 updated 객체로 반환값을 받는다.

  4. 서비스 클래스에 update() 메서드를 만들고 메모장에 붙여 넣었던 코드를 그대로 가져온다.

  5. 잘못된 요청을 처리하는 부분에서 return값을 null로 바꾼다. 잘못된 요청은 컨트롤러가 처리할 것이기 때문이다.

  6. 마지막 업데이트 부분도 return값을 updated로만 바꾼다. 저장된 데이터만 반환하고 응답은 컨트롤러가 할 것이기 때문이다.

게시글 삭제 요청 개선하기

  1. delete() 메서드의 주석을 모두 해제한다.

  2. 그리고 모든 코드를 잘라내 메모장에 붙여 넣는다.

  3. ArticleService의 delete(id) 메서드를 호출하고, deleted 객체로 받아온다.

  4. 서비스 클래스에 delete() 메서드를 만들고 메모장에 붙여 넣었던 코드를 그대로 가져온다.

  5. 잘못된 요청을 처리하는 코드에서 return 값으로 null을 설정한다. 마찬가지로 응답은 컨트롤러가 하기 때문이다.

  6. 마지막으로 삭제한 대상을 컨트롤러에 보내주기 위해 return값으로 target을 반환한다.

12.3 트랜잭션 맛보기

트랜잭션은 반드시 성공해야만 하는 일련의 과정이다. 만약 성공하지 못하면 원래 상태로 복구, 다시 말해 롤백된다. 정말 그런지 확인해보기 위해 다음과 같은 시나리오로 테스트 해본다.

  1. 게시판에 데이터 3개를 한꺼번에 생성 요청하기
  2. 데이터를 DB에 저장하는 과정에서 의도적으로 오류 발생시키기
  3. 어떻게 롤백되는지 확인하기

다음의 데이터를 http://localhost:8080/api/transaction-test로 POST 요청을 보낼것이다.


[
	{
		“title”: “시간 예약” ,
		“content”: “11111”
	},
	{
		“title”: “테이블 지정” ,
		“content”: “22222”
	},
	{
		“title”: “메뉴 선택” ,
		“content”: “33333”
	}
]
  1. 데이터 3개의 생성 요청을 받아 결과를 응답하도록 ArticleApiController에 transactionTest()라는 메서드를 추가한다.
  2. @PostMapping으로 “/api/transaction-test”의 데이터 생성 요청을 받는다.
  3. 매개변수로는 ArticleForm 데이터를 List로 묶은 dtos를 선언한다. REST API 방식으로 POST 요청을 받고 있으므로 @RequestBody도 붙여준다.
  4. 서버에서 응답할 때 데이터 생성 결과뿐만 아니라 상태 코드도 함께 보내므로 메서드의 반환형은 ResponseEntity<List<Article>>로 한다.
  5. articleService의 createArticles() 메서드를 호출한다. 매개변수로 받은 dtos도 함께 전달하고 반환값을 createdList라는 리스트에 저장한다.
  6. 생성 결과에 따라 응답을 처리한다.
  7. 서비스의 createArticles() 메서드를 작성한다.
    1. dto 묶음(리스트)을 엔티티 묶음(리스트)으로 변환하기
    2. 엔티티 묶음을 DB에 저장하기
    3. 강제로 에러를 발생시키기
      • findById()로 id가 -1인 데이터를 찾고 없으면 orElseTrow() 메서드로 IllegalArgumentException을 발생시키고 “결제 실패!”라는 메시지를 함께 남긴다.
    4. 결과 값 반환하기
      • 형식상 articleList를 반환해준다.

여기까지 하고 서버를 재시작한 후 Talend API Tester에서 데이터3개로 POST 요청을 보내면 상태 코드 500을 응답하고 에러가 났다는 것을 의미한다.

하지만 데이터 생성에 실패하였는데, DB에는 3개의 데이터가 담겨있는 것을 확인할 수 있다.

따라서 실패했을 경우 데이터가 남지 않게 하기 위해 createArticles() 메서드 위에 @Transactional을 붙인다.

  • 서비스의 메서드에 @Transactional을 붙이면 해당 메서드는 하나의 트랜잭션으로 묶이고 메서드가 중간에 실패하더라도 롤백을 통해 이전 상태로 돌아갈 수 있다.
  • 동일한 이름의 어노테이션이 여러개이므로 어노테이션에 마우스를 올린 후 Alt + Enter를 누르고 Transactional (org.springframework.transaction.annotaion)을 선택한다.

서버를 재시작하고 확인해보면 에러가 발생한 후 데이터가 DB에 저장되지 않는 것을 확인할 수 있다.

0개의 댓글