v1
★ MILESTONE · DB

첫 DB 종단간

회원 한 줄을 DB 에서 꺼내 화면으로

학습 목표

  • v0.5 의 하드코딩 자리에 Mapper 호출을 끼워넣는다
  • DB 의 데이터가 화면까지 흘러오는 6 단계를 짚을 수 있다
  • v0.5 와 v1 의 코드 차이를 라인별로 비교한다
  • 「DB 안 됨」 오류를 만났을 때 어디부터 점검할지 안다

⚠️ 지금까지의 한계

v0.5 의 빈 자리

v0.5 에서 우리가 만든 GreetService:


public String getGreeting(String name) {
    return "안녕하세요, " + name + "님!";  // ← 하드코딩
}

이름은 폼에서 받지만, 진짜 회원 정보는 어디서? 결국 DB 가 필요합니다.

🛠️ 오늘 만들 것

「회원 한 명 보기」 기능

URL /member/view?id=hong 접속 → DB 에서 id=hong 회원 정보 조회 → 화면에 표시.

요청: GET /member/view?id=hong ↓ [Controller] view(id="hong") ↓ [Service] find("hong") ↓ [Mapper] selectOne("hong") ↓ SQL: SELECT id, pwd FROM mymember WHERE id='hong' [DB] mymember 테이블에서 한 행 찾음 ↓ 결과 객체로 자동 변환 [Mapper] Member 객체 반환 ↓ [Service] 반환 ↓ [Controller] model.addAttribute("member", ...) ↓ [JSP] ${member.id}, ${member.pwd} ↓ 화면: "hong / temp1234"

① Member VO (그릇) — v2 최소형


package com.smhrd.domain;

import lombok.Data;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

@Data @AllArgsConstructor @NoArgsConstructor
public class Member {
    private String id;
    private String pwd;
}

👉 DB mymember 의 한 행이 자바 객체로 담길 그릇. 처음에는 두 칸이 전부.

② Mapper 인터페이스 (DB 접근 약속)


package com.smhrd.mapper;

import com.smhrd.domain.Member;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface MemberMapper {
    Member selectOne(String id);
}

👉 메서드 시그니처만 선언. 실제 SQL 은 다음 슬라이드의 XML 에.

③ Mapper XML (실제 SQL)


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.smhrd.mapper.MemberMapper">

    <select id="selectOne"
            parameterType="string"
            resultType="com.smhrd.domain.Member">
        SELECT id, pwd
        FROM mymember
        WHERE id = #{id}
    </select>

</mapper>

③ XML 의 핵심 4 줄

  • namespace="com.smhrd.mapper.MemberMapper" → 인터페이스의 풀 클래스명과 정확히 일치
  • id="selectOne" → 인터페이스 메서드 이름과 정확히 일치
  • resultType="com.smhrd.domain.Member" → 결과를 매핑할 자바 클래스
  • #{id} → 메서드 매개변수 값을 안전하게 바인딩 (PreparedStatement)
한 글자라도 틀리면

"Invalid bound statement" 또는 "BindingException" 오류. 인터페이스와 XML 의 namespace·id 매칭은 100% 정확해야 합니다.

④ Service (비즈니스 로직)


package com.smhrd.service;

import com.smhrd.domain.Member;
import com.smhrd.mapper.MemberMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class MemberService {

    @Autowired
    private MemberMapper mapper;

    public Member find(String id) {
        Member m = mapper.selectOne(id);
        if (m == null) {
            throw new IllegalArgumentException("회원이 없습니다: " + id);
        }
        return m;
    }
}

⑤ Controller


package com.smhrd.controller;

import com.smhrd.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class MemberController {

    @Autowired
    private MemberService service;

    @GetMapping("/member/view")
    public String view(@RequestParam String id, Model model) {
        model.addAttribute("member", service.find(id));
        return "member/view";
    }
}

⑥ JSP


<!-- /WEB-INF/views/member/view.jsp -->
<%@ page contentType="text/html; charset=UTF-8" %>
<html>
<head><title>회원 정보</title></head>
<body>
<h1>${member.id}</h1>
<table>
    <tr><th>아이디</th><td>${member.id}</td></tr>
    <tr><th>비밀번호</th><td>${member.pwd}</td></tr>
</table>
</body>
</html>

👉 ${member.id} = 모델의 member 객체의 id 필드 (getId() 호출).

v0.5 → v1 핵심 비교

v0.5 — 하드코딩

@Service
public class GreetService {
    public String getGreeting(String name) {
        return "안녕하세요, " + name + "!";
    }
}
v1 — DB 호출

@Service
public class MemberService {
    @Autowired
    private MemberMapper mapper;

    public Member find(String id) {
        return mapper.selectOne(id);
    }
}

Service 가 인자를 가공해 반환하는 골격은 같음. 「가공」이 「DB 조회」로 바뀐 것뿐.

흐름도 — 어디가 늘어났나

단계v0.5v1
① 입력폼 입력URL ?id=1
② Controller받음받음
③ Service하드코딩Mapper 호출
Mapper(없음)SQL 실행
DB(없음)데이터 응답
⑥ Service 받기(없음)Member 객체
⑦ Controller모델에 담기모델에 담기
⑧ JSP${msg}${member.id}

👉 새 박스 2 개(Mapper, DB) + 데이터 형태 변환 1 회.

실행해보기

  1. DB 에 회원 한 명 INSERT (수동):
    INSERT INTO mymember(id, pwd)
    VALUES('hong', 'temp1234');
  2. Tomcat 시작
  3. 브라우저: http://localhost:8080/.../member/view?id=hong
  4. 화면: "hong" + 비밀번호

「DB 안 됨」 일 때 점검 6 곳

증상의심할 곳
"Cannot create connection"① pom.xml ② DB URL/사용자/비밀번호 ③ MySQL 가동
"BindingException Invalid bound statement"④ Mapper 인터페이스 namespace ↔ XML namespace
"BindingException ... method not found"⑤ Mapper 메서드명 ↔ XML id
필드값이 모두 null⑥ 컬럼명 ↔ VO 필드명 (카멜/스네이크)

👉 본 과정의 「DB 5 단계 지도」 차시가 이 6 곳을 모두 다룹니다.

SQL 로그 켜기 — 디버깅 강력 도구


<!-- log4j2.xml -->
<Logger name="jdbc.sqlonly" level="INFO" additivity="false">
    <AppenderRef ref="Console"/>
</Logger>

켜두면 Tomcat 콘솔에 매 요청의 실제 SQL 이 출력됨:


SELECT id, pwd
FROM mymember WHERE id = 'hong'

👉 「내가 짠 SQL 이 진짜 이거 맞나?」를 확인할 수 있는 디버깅의 핵심 도구.

v1 의 의미

오늘이 우리 프로젝트의 세 번째 마일스톤입니다.

  • v0 — Hello Servlet
  • v0.5 — DB 없이 종단간
  • v1 — 첫 DB 종단간 (오늘)
  • v2~v∞ — 회원/게시판/REST

v1 부터는 「DB 가 항상 곁에」 있습니다. 다음 차시들은 모두 이 종단간 구조 위에 기능을 추가합니다.

🔄 Before / After

Part 4 시작

DB·MyBatis 의 의미는 안다.

한 번도 종단간으로 연결해본 적 없음.

v1 — Part 4 끝

DB → Mapper → Service → Controller → JSP 가 한 번에 연결된다.

SQL 로그가 디버깅의 첫 도구.

이번 차시의 데이터 흐름

브라우저
Controller
Service
Mapper
DB
Mapper 와 DB 박스가 흐름 끝에 자리잡음 — 처음으로 양방향 종단간 완성

정리

오늘 들고 가는 것

  • v0.5 의 「하드코딩」이 v1 의 「Mapper 호출」로 자연스럽게 교체됨
  • VO·Mapper 인터페이스·Mapper XML·Service·Controller·JSP 6 부품의 협업
  • SQL 로그가 디버깅의 핵심 도구
  • 「DB 안 됨」 시 점검할 6 곳을 안다
  • v1 — 우리 프로젝트의 세 번째 마일스톤. 이제 DB 가 항상 곁에

다음 Part: 회원과 게시판 — v1 위에 기능을 단계적으로 쌓습니다.