22.Service 어노테이션

강재영's avatar
Aug 22, 2024
22.Service 어노테이션
 

1. @Service

역할

  • 비즈니스 로직 처리: @Service 어노테이션은 애플리케이션의 서비스 계층에 사용되며, 비즈니스 로직을 처리하는 데 중점을 둡니다. 이 계층은 컨트롤러에서 전달된 데이터를 처리하거나, 저장소 계층과 상호 작용하여 필요한 작업을 수행합니다.

주요 기능

  • 트랜잭션 관리: 일반적으로 서비스 계층에서 트랜잭션 관리가 이루어지며, 데이터베이스의 일관성을 보장하기 위해 @Transactional 어노테이션과 함께 사용됩니다.
  • 비즈니스 로직 캡슐화: 서비스 계층은 비즈니스 로직을 캡슐화하여, 다른 계층에서 사용할 수 있도록 합니다.
  • Lazy Initialization: 엔티티 연관 관계에서 Lazy 로딩이 필요한 경우, 서비스 계층에서 이를 처리하여 데이터베이스와의 상호 작용을 최적화합니다.
 

2. 예시

notion image
 

2.1 트랜잭션(Transaction)이란?

트랜잭션은 데이터베이스에서 하나의 작업 단위로 처리되는 일련의 연산을 의미합니다. 트랜잭션은 다음의 네 가지 속성, 즉 ACID 원칙을 따릅니다:
  • Atomicity (원자성): 트랜잭션 내의 모든 작업이 성공적으로 완료되거나, 하나라도 실패하면 전체 트랜잭션이 롤백(되돌리기)되어야 합니다. 즉, 모든 작업이 하나의 단위로 간주되어, 모두 성공하거나 모두 실패해야 합니다.
  • Consistency (일관성): 트랜잭션이 시작되기 전과 완료된 후에 데이터베이스는 일관된 상태를 유지해야 합니다. 일관성은 데이터 무결성을 보장합니다.
  • Isolation (고립성): 동시에 실행되는 트랜잭션은 서로 영향을 미치지 않도록 격리됩니다. 하나의 트랜잭션이 완료되기 전까지는 다른 트랜잭션이 그 결과를 볼 수 없습니다.
  • Durability (영속성): 트랜잭션이 완료되면, 그 결과는 영구적으로 데이터베이스에 반영되어야 합니다. 시스템이 고장 나더라도 트랜잭션의 결과는 손실되지 않습니다.
 
💡
서비스 계층에서 트랜잭션으로 작업을 묶는 이유는 데이터 일관성, 오류 시 자동 롤백, 고립성 보장, 비즈니스 로직의 캡슐화를 위해서입니다.
 
 

단일 책임 원칙 (Single Responsibility Principle, SRP)

 
💡
SRP는 객체지향 설계의 중요한 원칙 중 하나로, 클래스나 모듈은 오직 하나의 책임만을 가져야 한다는 것을 의미합니다. 즉, 하나의 클래스는 하나의 기능 또는 역할만을 수행하며, 그 역할을 변경해야 할 이유가 한 가지뿐이어야 한다는 것입니다.
  • 컨트롤러의 책임: 클라이언트 요청을 처리하고, 서비스 계층과 상호작용하여 결과를 응답으로 반환하는 것.
  • 서비스의 책임: 비즈니스 로직을 처리하고, 필요한 데이터를 조작하거나 계산하는 것.
  • 레포지토리의 책임: 데이터베이스와 상호작용하여 데이터를 CRUD(Create, Read, Update, Delete)하는 것.
 

Service를 추가하여 리펙토링

 
@RequiredArgsConstructor @Service public class BoardService { private final BoardRepository boardRepository; //자바는 2가지를 리턴을 못한다. //Lazy전략이니 여기서 다 db를 떙겨온다. public BoardReponse.DetailDTO 상세보기(int id, User sessionUser) { Board board = boardRepository.findById(id); //조인(Board - User) return new BoardReponse.DetailDTO(board, sessionUser); } }
 
화면에 뿌려질 데이터를 생각해서 dto를 생성한다.
 
package shop.mtcoding.blog.board; import lombok.Data; import shop.mtcoding.blog.user.User; public class BoardReponse { @Data public static class DetailDTO { private Integer boardId; private String title; private String content; private Boolean isOwner; private Integer userId; private String username; public DetailDTO(Board board, User sessionUser) { this.boardId = board.getId(); this.title = board.getTitle(); this.content = board.getTitle(); this.isOwner = false; if (board.getUser().getId() == sessionUser.getId()) { isOwner = true; // 권한체크 } this.userId = board.getUser().getId(); this.username = board.getUser().getUsername(); } } }
 
정적 중첩 클래스를 만들어 외부 클래스 인스턴스를 생성안해도 new가 가능하게 해준다.
또한 entity객체에 없는 isOwner필드를 만들고 매개변수로 sesstion을 확인해서 권한체크를 해서 Boolean타입을 만들고 이를 머스테치 템플릿을 사용할때 사용할 수 있게 추가했다.
또한 필요없는 user의 password는 생략했다.
 
완성된 컨트롤러 모습
@GetMapping("/board/{id}") public String detail(@PathVariable("id") Integer id, HttpServletRequest request) { /*Board board = boardRepository.findById(id); request.setAttribute("model", board); request.setAttribute("isOwner", false);*/ User sessionUser = (User) session.getAttribute("sessionUser"); BoardReponse.DetailDTO detailDTO = boardService.상세보기(id, sessionUser); request.setAttribute("model", detailDTO); return "board/detail"; }
 
Share article

강재영 블로그