MVC 패턴과 JDBC가 적용된 프로젝트를 실습하면서 배운 내용들을 정리해본다.
중요한 포인트는 아래 두 가지이다.
- 호출 흐름이 어떻게 이루어지는지
- 이때 각 레이어에서 어떤 역할을 수행하는지
1. 전체 구조 파악하기

2. 레이어 역할
레이어 역할
- View: 프로그램 시작점(콘솔 UI). Controller 호출하고 결과 출력
- Controller: 요청 처리 + 응답 포맷팅(ResponseDto) 담당
- Service: 비즈니스 로직 + 트랜잭션(commit/rollback) 관리
- DAO: JDBC(SQL 실행)만 담당. Connection은 외부(Service)에서 주입받음
- DTO: 계층 간 데이터 전달용 객체
- DBUtil: DB 연결 설정/생성 담당
호출 흐름
[요청]
View → Controller → Service → Dao → DB
[응답]
Dao 결과 → Service 가공/트랜잭션 종료 → Controller가 ResponseDto로 포장 → View 출력
3. 왜 레이어를 나눴나?
(1) 관심사 분리(Separation of Concerns)
- SQL/JDBC 코드는 DAO에만
- 트랜잭션/업무 흐름은 Service에만
- 사용자가 볼 메시지/응답 형식은 Controller에만
- 출력/실행은 View에만
즉, “한 파일에 다 넣으면” 수정이 어려워지는 문제를 방지.
(2) 변경에 강함
- DB 쿼리 변경 → DAO만 수정
- 트랜잭션 정책 변경 → Service만 수정
- 응답 형식 변경 → Controller의 ResponseDto만 수정
- 출력 방식 변경(콘솔 → 웹/REST) → View/Controller 중심으로 수정
4. 레이어별 책임 분석
View
- UI 역할
- Controller 호출 결과를 그대로 출력
특징:
- View는 DB나 SQL을 전혀 모름
- 어떤 일이 일어나는지를 모른 채 호출과 출력 반복
Controller
View와 Service 사이에서
입력과 출력을 각각 변환해주는 어댑터(중간 변환 계층) 역할을 한다.
입력 변환
사용자의 입력을 Service가 처리하기 쉬운 객체(DTO)로 변환
DeptDto dto = new DeptDto(deptno, dname, loc);
DeptService.insertDept(dto);
💡 Tip
객체화(DTO)가 반드시 Controller로 국한되는 것은 아니다. 때로는 Service에서 할 수도 있다.
Service에서 객체화를 선택할 만한 상황
1. 입력값 조합/가공이 업무 로직에 가깝게 붙어 있을 때
- 예: 비밀번호 해싱, 기본값 세팅, 상태값 계산, 생성 규칙 적용 등
2. 요청 DTO와 DB DTO가 다른 모델이라 변환이 필요할 때
- 요청은 {dname, loc}만 오고, Service에서 deptno 생성하는 케이스 등
출력 변환
Service의 처리 결과를 View가 출력하기 쉬운 형태(ResponseDto)로 감싸 전달 -> 응답의 표준화
deptList = DeptService.getAllDepts();
return ResponseDto.success("모든 부서 검색 성공", deptList);
Service
비즈니스 로직을 수행하는 가장 중요한 계층
- DB 연결 생성(DBUtil.getConnection())
- 여러 DAO 호출을 “하나의 업무 단위”로 묶음
- 트랜잭션 경계 설정: setAutoCommit(false) → commit/rollback
- DAO 결과를 boolean 등으로 해석해서 반환
트랜잭션 처리 위치가 Service인 이유
DAO는 “하나의 SQL 실행”만 담당하고,
Service는 “업무 흐름” 단위를 담당함.
업무 단위 예시:
- 부서 생성(insert) + 로그 테이블 insert
- 부서 삭제 + 관련 데이터 삭제
이런 경우를 대비하면 트랜잭션은 DAO가 아니라 Service가 잡아야 함.
예외 처리 방식
- Service는 SQLException 발생시 rollback을 수행하고 다시 Exception을 던진다(throw)
- 즉, Service는 예외를 해결하지 않고 정리(rollback) 후 위임한다
Service는 트랜잭션 자원을 책임지므로, 실패 시 rollback까지 수행한 뒤 예외를 상위로 던진다.
⚠️ 만약 코드를 조금 더 고도화해서 다양한 예외를 처리하게 된다면?
| 예외 종류 | 생성 위치 | 이유 |
| SQLException | DAO | DB 기술 문제 |
| DataAccessException | Service | 기술 예외를 비즈니스 의미로 변환 |
| NotFoundException | Service | 비즈니스 규칙 판단 |
| ValidationException | Service 또는 Controller | 입력 검증 위치에 따라 다름 |
❓ DataAccessException 은 뭘까
지금까지 catch 절에서 자주 활용했던 SQLException은 약간의 단점을 갖고 있다.
- 너무 기술적
- DB 밴더마다 다름
- 내부 정보 중심
이런 지루하고 현학적인 기술 예외를 Controller 까지 올려서 처리하게 되면 피곤해진다.
이걸 처리할 수 있다는 것은 Controller가 DB 구조를 알고있어야 한다는 뜻이기도 하다 -> 계층 간 결합도 증가
또 메시지가 그대로 사용자에게 노출되는 것도 좋지 않다.
그래서 DataAccessException은 무엇이냐
목적 1. 기술 예외를 숨기기(추상화)
SQLException (DB 기술 예외)
↓
DataAccessException (의미 중심 예외)
상위 레이어에서는 "데이터 접근 중 오류가 발생했습니다" 정도만 확인할 수 있으면 적당하다.
목적 2. 계층 간 결합도 낮추기
아래 코드처럼 Controller가 SQLException을 처리하는 것을 피해야 한다
catch (SQLException e) {
if (e.getErrorCode() == 1062) { // MySQL duplicate key
...
}
}
애초에 위의 Exception을 처리하려면 1062라는 에러코드를 알고 있어야 한다. 이런 구조를 계층붕괴라고한다.
그래서 코드를 조금 더 고도화 한다면 아래처럼 처리하는 것이 바람직하겠다.
DAO → SQLException
Service → DataAccessException으로 변환
Controller → 의미 기반 처리
DAO
- Connection을 외부에서 주입받음
- SQL 준비/실행
- ResultSet → DeptDto 변환
- 자원 해제는 try-with-resources로 자동 처리
DTO: Dto & ResponseDto
Dto
- DB row를 담는 데이터 상자
- 계층 간 전달을 위해 존재
- 로직이 없고 데이터만 가짐
ResponseDto<T>
Controller의 출력 정책을 표준화
- status: SUCCESS / FAIL / NOT_FOUND 등을 담을 수 있게 설계
- msg: 사용자에게 보여줄 메시지
- data: 실제 결과 데이터
콘솔에서 뿐만 아니라 REST API의 응답 포맷과 유사하다
{
"status": "SUCCESS",
"msg": "...",
"data": {...}
}
ResponseDto는 “응답을 일관되게 관리”하기 위한 DTO이며, View는 status/msg/data 규약만 알면 된다.
'Learning Log' 카테고리의 다른 글
| [멋사 클라우드 5기] Day 17 - Flexbox (0) | 2026.02.13 |
|---|---|
| [멋사 클라우드 5기] Day 16 - 브라우저의 동작 원리: 주소창에서 화면 렌더링까지 (0) | 2026.02.12 |
| [멋사 클라우드 5기] Day 14 - Modeling, JDBC 이해하기 (1) | 2026.02.09 |
| [멋사 클라우드 5기] Day 13 - DML, DDL, DCL, TCL (0) | 2026.02.08 |
| [멋사 클라우드 5기] Day 12 - Join, Subquery, Union (0) | 2026.02.06 |
