Backend 담당 — Servlet과 DTO로 데이터 다루기
이 페이지는 Backend 담당 학생을 위한 전용 가이드입니다. Frontend의 form 데이터를 받아서 DB로 흘려보내는 중간 다리, 그게 Backend의 역할이에요. 여기서는 그 다리를 만들기 위한 필수 도구 — DTO 와 Servlet — 을 처음부터 다룹니다. ← 협업 메인으로 돌아가기
한 줄 요약
HTTP 요청에 담긴 흩어진 문자열들 → 깔끔한 Java 객체(DTO) → DB 쿼리 호출. 이 세 단계의 변환을 책임지는 게 Backend의 진짜 일이에요. URL → JSP 연결은 부수적입니다.
먼저 알아야 할 것 — DTO란 무엇인가?
DTO = "데이터를 담는 그릇"
DTO 는 Data Transfer Object 의 줄임말이에요. 직역하면 "데이터 전달 객체", 쉽게 말하면 "DB 데이터를 온전히 받을 빈 그릇" 입니다.
왜 그릇이 필요할까요? 회원 한 명의 정보를 생각해보세요. 아이디, 비밀번호, 닉네임, 이메일, 연락처, 취미, 색깔, 직업… 흩어진 변수가 8개나 됩니다. 메서드 사이를 이동할 때 매번 이 8개를 따로따로 들고 다니면 매개변수가 8개인 끔찍한 함수가 됩니다:
// 그릇이 없으면 — 매개변수 지옥 public void insert(String userId, String pw, String nickname, String email, String phone, String hobbies, String favoriteColor, String job) { // ... 8개를 다 일일이 받아야 함 }
그래서 이 8개를 한 덩어리로 묶어서 들고 다니는 그릇이 필요합니다. 그게 DTO 클래스예요:
// 그릇이 있으면 — 한 덩어리 public void insert(Member member) { // member 안에 8개 필드가 다 들어있음 }
DTO의 모양 — 필드 + getter/setter 만으로 충분
DTO는 굉장히 단순한 클래스예요. 로직은 전혀 없고, 데이터를 담을 칸(필드)과 그 칸에 값을 넣고 빼는 메서드(setter / getter)만 있습니다. 이게 다입니다.
// src/main/java/com/edu/model/Member.java package com.edu.model; public class Member { // ① 필드 — DB 컬럼과 1:1 매칭 private String userId; private String password; private String nickname; private String email; private String phone; private String hobbies; // CSV: "독서,운동,영화" private String favoriteColor; private String job; // ② setter — 값을 그릇에 넣는 메서드 public void setUserId(String userId) { this.userId = userId; } public void setPassword(String password) { this.password = password; } public void setNickname(String nickname) { this.nickname = nickname; } public void setEmail(String email) { this.email = email; } public void setPhone(String phone) { this.phone = phone; } public void setHobbies(String hobbies) { this.hobbies = hobbies; } public void setFavoriteColor(String c) { this.favoriteColor = c; } public void setJob(String job) { this.job = job; } // ③ getter — 그릇에서 값을 꺼내는 메서드 public String getUserId() { return userId; } public String getPassword() { return password; } public String getNickname() { return nickname; } public String getEmail() { return email; } public String getPhone() { return phone; } public String getHobbies() { return hobbies; } public String getFavoriteColor() { return favoriteColor; } public String getJob() { return job; } }
DB 컬럼 ↔ DTO 필드 매핑 규칙
DTO의 필드 이름은 보통 DB 컬럼 이름과 1:1로 매칭되도록 짓습니다. 다만 명명 규칙(naming convention)이 다를 뿐이에요:
| DB 컬럼 (스네이크 케이스) | DTO 필드 (카멜 케이스) | 매핑 방법 |
|---|---|---|
user_id | userId | 스네이크 → 카멜 변환 |
password | password | 동일 |
nickname | nickname | 동일 |
favorite_color | favoriteColor | 스네이크 → 카멜 변환 |
reg_date | regDate | 스네이크 → 카멜 변환 |
이 매핑은 MyBatis가 자동으로 해주지 않습니다. Day 1 회의에서 두 가지 방식 중 하나를 골라야 해요:
- 방법 A — Mapper XML에서 alias 사용:
SELECT user_id AS userId ... - 방법 B — MyBatis 설정 켜기:
mapUnderscoreToCamelCase=true(한 번만 켜면 자동 변환)
DTO를 어떻게 사용하는가 — 4단계
// ① 빈 그릇 만들기 Member newMember = new Member(); // ② 그릇에 값 채워넣기 (setter 사용) newMember.setUserId("test_user"); newMember.setPassword("1234"); newMember.setNickname("테스트유저"); newMember.setEmail("test@example.com"); // ... 나머지 필드도 // ③ 그릇을 통째로 다른 메서드에 넘기기 memberDao.insert(newMember); // 변수 8개 대신 그릇 1개 // ④ 다른 곳에서 그릇 안의 값 꺼내기 (getter 사용) String name = newMember.getNickname(); System.out.println("안녕하세요 " + name + "님");
DTO와 다른 클래스들 — 흔히 혼동하는 용어 정리
| 약어 | 풀네임 | 역할 |
|---|---|---|
| DTO | Data Transfer Object | 데이터를 담는 그릇 (오늘 배운 거) |
| VO | Value Object | DTO와 거의 같음 (옛날 용어, 학원에 따라 혼용) |
| DAO | Data Access Object | DB에 SQL을 실행하는 클래스 (MemberDao) |
| Servlet | — | HTTP 요청을 받는 클래스 (RegisterServlet) |
대부분의 한국 JSP 교재에서는 DTO와 VO를 같은 의미로 씁니다. 둘 다 "데이터 그릇" 이에요. DAO 는 다릅니다 — DAO는 그릇이 아니라 그릇을 들고 DB로 왔다 갔다 하는 "심부름꾼" 입니다. 헷갈리지 마세요.
Backend의 진짜 역할 — Controller가 하는 일
Backend
Controller의 진짜 역할 — "페이지 연결"이 아니라 "데이터 번역"
학생들이 가장 많이 오해하는 지점: "Controller는 URL에 맞는 JSP로 보내주는 거" — 반만 맞습니다.
Controller의 80% 일은 받아온 데이터를 DB가 저장할 수 있는 형태로 가공해서 넘기는 것 이에요.
JSP로 보내는 건 마지막 한 줄(sendRedirect)에 불과합니다.
회원가입 Controller 하나가 실제로 하는 일들:
- ① 추출: form 파라미터 문자열들을
String userId = req.getParameter("userId")로 변수에 담음 - ② 검증: 빈 값 체크, 비번 일치 확인, 이메일 형식 등 "이 데이터 DB에 넣어도 되는가?"
- ③ 조립: 여러 변수를 한 덩어리 DTO(Member 객체) 로 재조립 (HTTP → Java 객체 변환)
- ④ 전달:
memberDao.insert(newMember)로 DAO에 그릇을 통째로 넘김 - ⑤ 응답: 성공/실패에 따라 어느 JSP로 리다이렉트할지 결정 — 이게 "페이지 연결" 부분, 단 한 줄
작업 순서 — "Frontend를 기다리지 않는다"
- Step 0 — DTO(Member.java) 작성: 위에서 본 그릇 클래스부터 만들기. 모든 작업의 출발점.
- Step 1 — Frontend에서 form 명세 받기: Day 1 회의에서 받은 필드 목록.
- Step 2 — 자기 전용 "Simple Page" 만들기: 꾸밈 0, 순수 HTML form 태그만 나열된 페이지. Frontend 완성본이 아니에요. Backend 본인이 Servlet을 테스트하기 위한 임시 페이지 입니다.
- Step 3 — RegisterServlet 작성:
service에서req.getParameter로 값들을 변수에 담기. - Step 4 — 변수들을 DTO에 담고 콘솔 출력: Member 객체로 조립한 뒤
System.out.println으로 확인. 여기까지가 DB 쿼리 실행 이전 의 Backend 책임. - Step 5 — DB 담당에게 요청: "이 DTO를 member 테이블에 INSERT 하는 쿼리 필요해요"
- Step 6 — DB로부터 완성된 쿼리를 받아서 연결: Mapper XML에 넣고 Servlet에서 호출.
Step 2 · Backend 전용 Simple Page (꾸밈 0)
이 페이지는 Frontend가 만드는 예쁜 회원가입 화면과 다른 파일입니다. Backend가 자기 손에 쥐고 있는 테스트 도구예요. Tomcat을 띄우고 브라우저로 열어서 값 넣고 "가입" 눌러보기 용도. CSS는 아예 없고, 오직 form과 input 태그만. 새 필드들(checkbox·radio·select)도 모두 포함해서, 서버가 모든 입력 타입을 제대로 받는지 한 방에 확인할 수 있어요.
<%-- webapp/_backend_test/register_test.jsp --%> <%-- ↑ 파일명에 _ 를 붙여서 "테스트용" 임을 표시, 나중에 지울 거 --%> <!DOCTYPE html> <html><head><meta charset="UTF-8"><title>RegisterServlet 테스트</title></head> <body> <h3>[Backend 전용] 회원가입 Servlet 테스트 페이지</h3> <form action="register" method="post"> userId: <input name="userId"><br> password: <input name="password" type="password"><br> passwordConfirm: <input name="passwordConfirm" type="password"><br> nickname: <input name="nickname"><br> email: <input name="email"><br> phone: <input name="phone"><br> hobbies: <label><input type="checkbox" name="hobbies" value="독서">독서</label> <label><input type="checkbox" name="hobbies" value="운동">운동</label> <label><input type="checkbox" name="hobbies" value="영화">영화</label><br> favoriteColor: <label><input type="radio" name="favoriteColor" value="빨강">빨강</label> <label><input type="radio" name="favoriteColor" value="파랑">파랑</label><br> job: <select name="job"> <option value="학생">학생</option> <option value="회사원">회사원</option> </select><br><br> <button type="submit">전송</button> </form> </body></html>
join.jsp 가 아직 안 나왔을 수도 있고,
나왔더라도 CSS/JS 때문에 디버깅이 어려울 수도 있거든요. Backend는 "오직 값이 제대로 넘어오는가" 만 확인하면 되니까,
가장 단순한 form 한 장이 최고의 테스트 도구입니다.
Step 3-4 · RegisterServlet — 다양한 입력 타입을 받아 DTO에 담기
여기가 Controller의 "데이터 번역" 이 눈에 보이는 지점입니다. text/radio/select는 평범하게 getParameter 로 받지만,
checkbox 복수 선택(hobbies) 은 getParameterValues 로 받아서 String 배열이 돌아옵니다. 이걸 DTO에 담기 좋은 형태인
CSV 문자열("독서,운동,영화")로 번역하는 것도 Controller의 일이에요.
// src/main/java/com/edu/controller/RegisterServlet.java package com.edu.controller; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.*; import com.edu.model.Member; // ← 우리가 만든 DTO @WebServlet("/register") public class RegisterServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); // 한글 깨짐 방지 (가장 먼저!) // ========== ① 단일 값 필드들 — getParameter ========== String userId = req.getParameter("userId"); String password = req.getParameter("password"); String passwordConfirm = req.getParameter("passwordConfirm"); String nickname = req.getParameter("nickname"); String email = req.getParameter("email"); String phone = req.getParameter("phone"); String favoriteColor = req.getParameter("favoriteColor"); // radio String job = req.getParameter("job"); // select // ========== ② checkbox 복수 선택 — getParameterValues ========== String[] hobbiesArr = req.getParameterValues("hobbies"); // ↑ 사용자가 아무것도 체크 안 하면 null 이 나옴. 주의! // ========== ③ String[] → CSV 문자열로 번역 ========== String hobbiesCsv; if (hobbiesArr == null || hobbiesArr.length == 0) { hobbiesCsv = ""; // 아무것도 선택 안 함 } else { hobbiesCsv = String.join(",", hobbiesArr); // "독서,운동,영화" } // ========== ④ 받은 값 확인 (디버깅) ========== System.out.println("========= 회원가입 요청 =========="); System.out.println("userId = " + userId); System.out.println("password = " + password); System.out.println("nickname = " + nickname); System.out.println("email = " + email); System.out.println("phone = " + phone); System.out.println("hobbies (raw) = " + java.util.Arrays.toString(hobbiesArr)); System.out.println("hobbies (csv) = " + hobbiesCsv); System.out.println("favoriteColor = " + favoriteColor); System.out.println("job = " + job); // ========== ⑤ 검증 — 비밀번호 일치 ========== if (!password.equals(passwordConfirm)) { resp.sendRedirect("join.jsp?error=pw_mismatch"); return; } // ========== ⑥ DTO(Member) 그릇에 담기 ========== Member newMember = new Member(); // 빈 그릇 newMember.setUserId(userId); // 칸 채우기 newMember.setPassword(password); newMember.setNickname(nickname); newMember.setEmail(email); newMember.setPhone(phone); newMember.setHobbies(hobbiesCsv); // CSV로 번역된 값 newMember.setFavoriteColor(favoriteColor); newMember.setJob(job); // ↑ 이제 newMember 안에 회원 정보 8개가 다 들어있음 // ========== ⑦ DAO에 그릇 통째로 넘기기 ========== // 아래 한 줄은 DB 담당이 쿼리를 줄 때까지 주석처리 // int result = memberDao.insert(newMember); resp.sendRedirect("join_success.jsp"); } }
① HTTP 문자열 파라미터 → Java String 변수 (
getParameter)② HTTP의 반복된 파라미터 → Java String 배열 (
getParameterValues)③ 배열 → CSV 문자열 (
String.join)④ 흩어진 String 변수들 → 한 덩어리 DTO 객체 (그릇에 담기)
이 4가지 번역이 끝나면 DAO에게 그릇 하나만 넘기면 됩니다. 깔끔하죠?
hobbies (raw) 는 배열이었지만 hobbies (csv) 로 번역됐음getParameter vs getParameterValues — 비교표
| 메서드 | 사용 시점 | 리턴 타입 | 값이 없을 때 |
|---|---|---|---|
getParameter("x") |
text, password, email, radio, select, 단일 checkbox | String |
null (파라미터 없을 때) 또는 "" (빈 값) |
getParameterValues("x") |
checkbox 복수 선택, 같은 name의 input이 여러 개 | String[] |
null (아무것도 선택 안 함) — 반드시 null 체크! |
getParameter("hobbies") 를 쓰면 첫 번째 체크된 값 하나만 돌아옵니다.
나머지는 조용히 버려져요. 복수 선택 checkbox는 반드시 getParameterValues.
Step 5 · DB 담당에게 넘기는 요청 (문서 또는 메신저)
// DB 담당에게 보낼 메모
[INSERT 쿼리 부탁드립니다]
테이블: member
DTO 클래스: com.edu.model.Member (이 그릇을 통째로 받아주세요)
DTO 필드 → DB 컬럼 매핑:
- userId → user_id
- password → password
- nickname → nickname
- email → email
- phone → phone
- hobbies → hobbies (CSV 문자열)
- favoriteColor → favorite_color
- job → job
- reg_date → DB가 자동 (DEFAULT CURRENT_TIMESTAMP)
리턴은 영향 받은 행 수(int) 로 주세요.
Step 6 · DB가 쿼리 주면 한 줄 바꾸기
아까 주석처리했던 memberDao.insert(newMember) 줄의 주석만 풀면 됩니다. 그 외 코드는 수정 없음.
Backend가 절대 하면 안 되는 것
2. Simple Page를 실제 배포 파일로 쓰기.
_backend_test/ 폴더는 Day 5 통합 테스트 후 삭제합니다.
프로덕션엔 Frontend의 예쁜 join.jsp 만 남습니다.3. Servlet 안에 SQL 직접 쓰기. SQL은 DB 담당의 영역. Servlet은 변수 받아서 DTO에 담고 DAO에 넘기기만.
4. 파라미터 null 체크 없이 바로 사용. form에서 빈 값이 오면
"" 이 아니라 null일 수도 있어요. NPE 주의.