[Spring] 08. @Controller, @Service, @Repository
이전 글에서는 @Component와 Component Scan에 대해 살펴봤습니다. Spring은 Component Scan을 통해 @Component 계열 어노테이션이 붙은 클래스를 찾아 Bean으로 등록합니다.
이번 글에서는 Spring에서 자주 사용하는 역할별 어노테이션인 @Controller, @Service, @Repository를 정리하겠습니다. 이 세 가지는 단순히 Bean으로 등록하기 위한 표시가 아니라, 각 클래스가 어떤 역할을 맡는지 드러내기 위해 사용합니다.
Spring 프로젝트를 안정적으로 만들려면 Controller, Service, Repository의 역할을 분리하는 것이 중요합니다.
1. 역할별 어노테이션이 필요한 이유
Spring에서는 클래스 위에 @Component를 붙이면 Bean 등록 대상으로 만들 수 있습니다. 하지만 모든 클래스에 @Component만 붙이면 코드의 역할이 잘 보이지 않습니다.
@Component
public class BoardController {
}
@Component
public class BoardService {
}
@Component
public class BoardRepository {
}
위 코드도 Bean 등록은 가능하지만, 어떤 클래스가 요청을 처리하고, 어떤 클래스가 기능 흐름을 담당하고, 어떤 클래스가 DB에 접근하는지 한눈에 보기 어렵습니다.
그래서 Spring에서는 역할에 맞는 어노테이션을 사용합니다.
@Controller
public class BoardController {
}
@Service
public class BoardService {
}
@Repository
public class BoardRepository {
}
이렇게 작성하면 클래스 이름과 어노테이션만 봐도 계층의 역할을 이해할 수 있습니다.
2. @Controller란?
@Controller는 사용자의 요청을 처리하는 클래스에 사용합니다. 웹 브라우저에서 들어온 요청을 받고, 필요한 처리를 Service에 맡긴 뒤, 결과 화면을 반환하는 역할을 합니다.
@Controller
public class BoardController {
private final BoardService boardService;
public BoardController(BoardService boardService) {
this.boardService = boardService;
}
}
Controller는 사용자의 요청과 가장 가까운 계층입니다. 예를 들어 게시글 목록 요청, 게시글 등록 요청, 게시글 상세 조회 요청 등을 받습니다.
사용자 요청
→ Controller
→ Service
→ Repository 또는 Mapper
→ DB
Controller는 요청을 받는 입구입니다. 하지만 모든 기능을 Controller 안에서 처리하면 코드가 금방 복잡해집니다. 그래서 실제 기능의 흐름은 Service에 맡기는 것이 좋습니다.
3. Controller가 담당하는 일
Controller는 주로 요청과 응답을 처리합니다.
| 요청 받기 | URL로 들어온 요청을 메서드와 연결합니다. |
| 파라미터 받기 | 사용자가 보낸 값을 받습니다. |
| Service 호출 | 실제 기능 처리를 Service에 맡깁니다. |
| 결과 전달 | 화면에 보여줄 데이터를 Model에 담거나 응답으로 반환합니다. |
| 화면 반환 | JSP, Thymeleaf 같은 View 이름을 반환합니다. |
간단한 게시글 목록 화면을 예로 들어보겠습니다.
@Controller
public class BoardController {
private final BoardService boardService;
public BoardController(BoardService boardService) {
this.boardService = boardService;
}
@GetMapping("/board/list")
public String list(Model model) {
List<BoardDto> boardList = boardService.findBoardList();
model.addAttribute("boardList", boardList);
return "board/list";
}
}
이 코드에서 Controller는 게시글 목록 조회 요청을 받고, Service를 호출하고, 조회 결과를 화면에 전달합니다. DB 조회를 직접 처리하지 않습니다.
4. @Service란?
@Service는 기능의 흐름과 규칙을 처리하는 클래스에 사용합니다. Controller가 요청을 받는 역할이라면, Service는 실제 기능이 어떻게 처리되어야 하는지 담당합니다.
@Service
public class BoardService {
private final BoardRepository boardRepository;
public BoardService(BoardRepository boardRepository) {
this.boardRepository = boardRepository;
}
}
Service는 비즈니스 로직을 처리하는 계층이라고도 합니다. 여기서 비즈니스 로직이란 단순한 문법이 아니라, 서비스의 규칙과 흐름을 의미합니다.
예를 들어 게시글 등록 기능에는 다음과 같은 규칙이 있을 수 있습니다.
- 제목은 비어 있으면 안 됩니다.
- 내용은 일정 길이 이상이어야 합니다.
- 로그인한 사용자만 게시글을 작성할 수 있습니다.
- 게시글 등록 후 작성자의 포인트를 증가시킬 수 있습니다.
- 게시글 저장과 포인트 적립은 하나의 흐름으로 처리되어야 합니다.
이런 규칙을 Controller에 모두 넣으면 Controller가 너무 무거워집니다. 그래서 기능의 규칙과 흐름은 Service에서 처리하는 것이 좋습니다.
5. Service가 담당하는 일
Service는 기능의 중심 흐름을 담당합니다.
| 기능 흐름 처리 | 게시글 등록, 회원가입, 주문 처리 같은 기능의 순서를 정리합니다. |
| 규칙 검증 | 입력값이나 권한 등 필요한 조건을 확인합니다. |
| Repository 호출 | DB 작업이 필요하면 Repository나 Mapper를 호출합니다. |
| 트랜잭션 처리 | 여러 DB 작업을 하나의 작업처럼 처리합니다. |
| 결과 반환 | Controller에 처리 결과를 돌려줍니다. |
게시글 목록 조회 Service 예시는 다음과 같습니다.
@Service
public class BoardService {
private final BoardRepository boardRepository;
public BoardService(BoardRepository boardRepository) {
this.boardRepository = boardRepository;
}
public List<BoardDto> findBoardList() {
return boardRepository.findAll();
}
}
지금 예시는 단순하지만, 기능이 복잡해질수록 Service 계층의 역할이 중요해집니다. Controller는 요청과 응답에 집중하고, Service는 기능 흐름에 집중해야 합니다.
6. @Repository란?
@Repository는 DB 접근을 담당하는 클래스에 사용합니다. Repository는 데이터를 저장하거나 조회하거나 수정하거나 삭제하는 역할을 합니다.
@Repository
public class BoardRepository {
public List<BoardDto> findAll() {
System.out.println("게시글 목록 조회");
return new ArrayList<>();
}
}
Repository는 DB와 가까운 계층입니다. Service에서 필요한 데이터를 요청하면 Repository가 DB에 접근해서 결과를 가져옵니다.
Service
→ Repository
→ DB 조회
→ 결과 반환
Spring JDBC나 JPA를 사용하는 구조에서는 Repository라는 이름을 자주 사용합니다. MyBatis를 사용하는 프로젝트에서는 Repository 대신 Mapper 인터페이스를 사용하는 경우도 많습니다.
7. Repository와 Mapper의 차이
백엔드 프로젝트를 하다 보면 Repository와 Mapper라는 단어가 함께 등장합니다. 둘 다 DB 접근과 관련이 있지만 사용하는 기술에 따라 표현이 달라질 수 있습니다.
| Repository | Spring JDBC, JPA 등에서 DB 접근 계층을 표현할 때 자주 사용합니다. |
| Mapper | MyBatis에서 SQL과 Java 메서드를 연결하는 인터페이스로 자주 사용합니다. |
MyBatis를 사용하는 경우에는 다음처럼 @Mapper를 붙일 수 있습니다.
@Mapper
public interface BoardMapper {
List<BoardDto> findAll();
}
이 경우 Service는 Repository 대신 Mapper를 주입받아 사용할 수 있습니다.
@Service
public class BoardService {
private final BoardMapper boardMapper;
public BoardService(BoardMapper boardMapper) {
this.boardMapper = boardMapper;
}
public List<BoardDto> findBoardList() {
return boardMapper.findAll();
}
}
즉, 개념적으로는 DB 접근 계층이고, 프로젝트에서 사용하는 기술에 따라 Repository 또는 Mapper라는 이름을 사용할 수 있습니다.
8. 전체 흐름 예시
게시글 목록 조회 기능을 전체 흐름으로 보면 다음과 같습니다.
1. 사용자가 /board/list 요청
2. BoardController가 요청을 받음
3. BoardController가 BoardService 호출
4. BoardService가 BoardRepository 또는 BoardMapper 호출
5. DB에서 게시글 목록 조회
6. 조회 결과를 Service로 반환
7. Service가 Controller로 반환
8. Controller가 Model에 담고 화면 반환
코드로 보면 다음과 같은 구조입니다.
@Controller
public class BoardController {
private final BoardService boardService;
public BoardController(BoardService boardService) {
this.boardService = boardService;
}
@GetMapping("/board/list")
public String list(Model model) {
List<BoardDto> boardList = boardService.findBoardList();
model.addAttribute("boardList", boardList);
return "board/list";
}
}
@Service
public class BoardService {
private final BoardRepository boardRepository;
public BoardService(BoardRepository boardRepository) {
this.boardRepository = boardRepository;
}
public List<BoardDto> findBoardList() {
return boardRepository.findAll();
}
}
@Repository
public class BoardRepository {
public List<BoardDto> findAll() {
return new ArrayList<>();
}
}
이 구조에서 각 계층은 자기 역할만 담당합니다. Controller는 요청과 응답, Service는 기능 흐름, Repository는 DB 접근을 담당합니다.
9. @RestController는 무엇일까?
Spring에서는 @Controller와 함께 @RestController도 자주 사용합니다.
@Controller는 보통 화면을 반환할 때 사용합니다. JSP나 Thymeleaf 같은 View 이름을 반환하는 구조에서 자주 사용합니다.
@Controller
public class BoardController {
@GetMapping("/board/list")
public String list() {
return "board/list";
}
}
반면 @RestController는 JSON 같은 데이터를 바로 응답할 때 사용합니다. REST API를 만들 때 자주 사용합니다.
@RestController
public class BoardApiController {
@GetMapping("/api/boards")
public List<BoardDto> list() {
return List.of();
}
}
| @Controller | 주로 화면 View를 반환합니다. |
| @RestController | 주로 JSON 데이터를 응답합니다. |
처음에는 화면을 반환하는 Controller와 데이터를 반환하는 RestController를 구분해서 이해하면 좋습니다.
10. 자주 하는 실수
역할별 어노테이션을 사용할 때 자주 하는 실수는 Controller에 모든 코드를 몰아넣는 것입니다.
@Controller
public class BoardController {
@GetMapping("/board/list")
public String list(Model model) {
// DB 연결
// SQL 실행
// 데이터 가공
// 검증
// 화면 전달
return "board/list";
}
}
이런 구조는 처음에는 빠르게 만들 수 있지만, 기능이 늘어나면 유지보수가 어려워집니다. 요청 처리, 기능 규칙, DB 접근이 한 클래스에 섞이기 때문입니다.
좋은 구조는 역할을 나누는 것입니다.
Controller
→ 요청과 응답 처리
Service
→ 기능 흐름과 규칙 처리
Repository / Mapper
→ DB 접근 처리
또 다른 실수는 DTO나 VO에 @Service, @Component를 붙이는 것입니다. DTO와 VO는 보통 데이터를 담는 객체이므로 Spring Container가 계속 관리해야 하는 Bean이 아닙니다.
Bean으로 등록하는 경우가 많은 클래스
→ Controller
→ Service
→ Repository
→ Mapper
→ 설정 클래스
보통 Bean으로 등록하지 않는 클래스
→ DTO
→ VO
→ 단순 요청 객체
→ 단순 응답 객체
11. 계층을 나누면 좋은 점
Controller, Service, Repository를 나누면 코드의 역할이 명확해집니다. 문제가 생겼을 때 어느 계층을 확인해야 하는지도 더 쉽게 판단할 수 있습니다.
| 요청 URL 문제 | Controller를 확인합니다. |
| 기능 처리 문제 | Service를 확인합니다. |
| DB 조회 문제 | Repository 또는 Mapper를 확인합니다. |
| 화면 출력 문제 | View와 Model 전달 값을 확인합니다. |
이렇게 역할을 나누면 기능이 커져도 구조를 유지하기 쉽습니다. 게시판에서 댓글, 좋아요, 파일 업로드, 검색, 페이징이 추가되어도 각 계층의 책임을 기준으로 코드를 배치할 수 있습니다.
정리
@Controller, @Service, @Repository는 모두 Spring Bean 등록 대상이 될 수 있는 @Component 계열 어노테이션입니다. 하지만 단순히 Bean 등록만을 위한 것이 아니라, 각 클래스의 역할을 명확하게 드러내기 위해 사용합니다.
| @Controller | 사용자의 요청을 받고 응답을 반환하는 계층 |
| @Service | 기능의 흐름과 규칙을 처리하는 계층 |
| @Repository | DB 접근을 담당하는 계층 |
| @RestController | JSON 같은 데이터를 응답하는 API 계층 |
Spring 프로젝트에서는 요청 처리, 기능 흐름, DB 접근을 한 클래스에 몰아넣지 않고 계층별로 분리하는 것이 중요합니다. 이 구조를 잘 잡아두면 나중에 Spring MVC, DB 연동, 트랜잭션, REST API를 공부할 때 훨씬 이해하기 쉬워집니다.
연습해보기
아래 기능을 만든다고 생각해보세요.
회원가입 기능
1. 사용자가 회원가입 폼을 제출한다.
2. 아이디 중복 여부를 확인한다.
3. 비밀번호를 암호화한다.
4. 회원 정보를 DB에 저장한다.
5. 가입 결과를 화면에 보여준다.
이 기능을 계층별로 나누면 다음처럼 생각할 수 있습니다.
| Controller | 회원가입 요청을 받고 결과 화면을 반환합니다. |
| Service | 중복 확인, 비밀번호 처리, 회원가입 흐름을 담당합니다. |
| Repository / Mapper | 회원 정보를 조회하거나 저장합니다. |
| DTO | 회원가입 요청 데이터를 담습니다. |
다음 글에서는 @Autowired와 생성자 주입에 대해 더 자세히 정리하겠습니다. 지금까지 배운 Bean과 계층 구조가 실제로 어떻게 연결되는지 코드 중심으로 확인해보겠습니다.

'Tech Stack > Spring Boot' 카테고리의 다른 글
| [Spring] 10. @Qualifier와 @Primary (0) | 2026.07.04 |
|---|---|
| [Spring] 09. @Autowired와 생성자 주입 (0) | 2026.07.03 |
| [Spring] 07. @Component와 Component Scan (0) | 2026.07.01 |
| [Spring] 06. Spring Container와 ApplicationContext (0) | 2026.06.30 |
| [Spring] 05. Bean이란? (0) | 2026.06.29 |