1. @Service
역할
- 비즈니스 로직 처리:
@Service
어노테이션은 애플리케이션의 서비스 계층에 사용되며, 비즈니스 로직을 처리하는 데 중점을 둡니다. 이 계층은 컨트롤러에서 전달된 데이터를 처리하거나, 저장소 계층과 상호 작용하여 필요한 작업을 수행합니다.
주요 기능
- 트랜잭션 관리: 일반적으로 서비스 계층에서 트랜잭션 관리가 이루어지며, 데이터베이스의 일관성을 보장하기 위해
@Transactional
어노테이션과 함께 사용됩니다.
- 비즈니스 로직 캡슐화: 서비스 계층은 비즈니스 로직을 캡슐화하여, 다른 계층에서 사용할 수 있도록 합니다.
- Lazy Initialization: 엔티티 연관 관계에서
Lazy
로딩이 필요한 경우, 서비스 계층에서 이를 처리하여 데이터베이스와의 상호 작용을 최적화합니다.
2. 예시

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