6주차 Unit 7.4 — RowMapper

Psj·2026년 6월 1일

F-lab

목록 보기
212/239

Unit 7.4 — RowMapper

F-LAB JAVA · 6주차 · Phase 7 · JdbcTemplate (반복 제거)


📌 학습 목표

이 Unit을 끝내면 다음을 답할 수 있어야 한다.

  • RowMapper 의 정의는?
  • 함수형 인터페이스 (3주차) 와의 연결은?
  • (rs, rowNum) -> 람다 표현은?
  • BeanPropertyRowMapper 동작은?
  • snake_case → camelCase 변환은?
  • 5주차 전략 패턴 의 적용은?
  • 매퍼 재사용 방법은?
  • RowMapper vs ResultSetExtractor 차이는?
  • 매핑이 "변하는 부분" 의 대표 사례인 이유는?

🎯 핵심 한 문장

RowMapper 는 ResultSet 의 한 행을 객체로 매핑하는 함수형 인터페이스로 (rs, rowNum) → 객체 람다 한 줄로 표현할 수 있으며 3주차 람다·함수형 인터페이스의 실제 사례이고, BeanPropertyRowMapper 는 snake_case 컬럼을 camelCase 필드로 자동 매핑해주며 매핑은 5주차 전략 패턴의 "변하는 부분" 의 가장 명확한 사례다.
RowMapper 는 ResultSet 의 한 행을 자바 객체로 매핑하는 함수형 인터페이스 다.
메서드는 단 하나 — T mapRow(ResultSet rs, int rowNum) throws SQLException — 이라서 람다로 (rs, rowNum) -> { ... } 한 줄로 표현할 수 있고, 이는 3주차 함수형 인터페이스/람다 의 실제 활용 사례다.
Spring 의 BeanPropertyRowMapper 는 DB 의 snake_case 컬럼명을 자바 객체의 camelCase 필드로 자동 매핑해줘 매퍼를 직접 작성할 필요가 없을 때 편리하다.
매핑은 5주차 전략 패턴의 "변하는 부분" 의 대표 사례 — JdbcTemplate (변하지 않는 흐름) + RowMapper (변하는 매핑 전략) 의 결합이며, 람다·메서드 참조·BeanPropertyRowMapper 등 다양한 방식으로 전달할 수 있다.

비유 — DB 행에서 객체로의 통역사

RowMapper = 통역사:

ResultSet 의 한 행:
  - DB 의 언어 (컬럼들)
  - bl_no = "BL001", weight = 100.5

RowMapper (통역사):
  - DB 언어 → 자바 객체 언어
  - 한 행 → 한 객체

람다 표현 (3주차):
  - 한 줄로 통역
  - (rs, n) -> new Shipment(rs.getLong("id"), ...)

BeanPropertyRowMapper (자동 통역사):
  - 이름 비슷하면 자동
  - bl_no → blNo (snake → camel)
  - 매퍼 직접 X

5주차 전략 패턴:
  - 매핑 방식 (전략)
  - JdbcTemplate 에 주입
  - 다양한 매퍼 (다양한 전략)

매핑 = 변하는 부분의 대표:
  - 같은 SQL, 다른 매핑
  - 같은 매핑, 다른 SQL
  - 분리 → 재사용

→ RowMapper = 행→객체 매핑 (함수형), 람다로 간결, BeanProperty 자동, 전략 패턴.


🧭 9개 섹션 로드맵

1. RowMapper 정의
2. 함수형 인터페이스 (3주차 연결)
3. 람다 표현
4. BeanPropertyRowMapper
5. snake_case → camelCase
6. 5주차 전략 패턴 적용
7. 매퍼 재사용
8. ResultSetExtractor 차이
9. 매핑 = 변하는 부분

1️⃣ RowMapper 정의

1.1 정의

RowMapper:

  ResultSet 의 한 행을:
    - 자바 객체로 매핑
    - 함수형 인터페이스

  org.springframework.jdbc.core.RowMapper

1.2 인터페이스

// RowMapper 인터페이스 (Spring)
@FunctionalInterface
public interface RowMapper<T> {
    T mapRow(ResultSet rs, int rowNum) throws SQLException;
}
// 단 하나의 추상 메서드
// → 람다 가능

1.3 메서드 파라미터

mapRow(rs, rowNum):

  rs: 현재 행 가리키는 ResultSet
    - rs.getLong("id")
    - rs.getString("bl_no")
    - 등

  rowNum: 행 번호 (0부터)
    - 잘 안 씀
    - 매핑에 행 번호 필요 시

1.4 반환

반환:

  T: 매핑된 객체
    - Shipment, Booking 등
    - 한 행 → 한 객체

→ 변환 결과

1.5 ILIC 의 맥락

// RowMapper (ILIC)
RowMapper<Shipment> shipmentMapper = new RowMapper<Shipment>() {
    @Override
    public Shipment mapRow(ResultSet rs, int rowNum) throws SQLException {
        Shipment s = new Shipment();
        s.setId(rs.getLong("id"));
        s.setBlNo(rs.getString("bl_no"));
        s.setWeight(rs.getBigDecimal("weight"));
        s.setStatus(rs.getString("status"));
        return s;
    }
};
// 익명 클래스 (Java 7 이전)
// Java 8+ 에선 람다 (다음 섹션)
class Shipment {
    void setId(Long id) {}
    void setBlNo(String s) {}
    void setWeight(java.math.BigDecimal w) {}
    void setStatus(String s) {}
}
interface RowMapper<T> { T mapRow(ResultSet rs, int rowNum) throws SQLException; }

1.6 자기 점검 답변

RowMapper 의 정의는?

:
1. RowMapper:

  • 행 → 객체
  1. 인터페이스:

    • 함수형
  2. 메서드:

    • mapRow(rs, n)
  3. 반환:

    • 매핑 객체

2️⃣ 함수형 인터페이스 (3주차 연결)

2.1 @FunctionalInterface

RowMapper = 함수형 인터페이스:

  @FunctionalInterface:
    - 추상 메서드 단 하나
    - 람다 가능
    - Java 8+

2.2 3주차 함수형 인터페이스 복습

3주차 함수형 인터페이스:

  - Function<T, R>
  - Predicate<T>
  - Consumer<T>
  - Supplier<T>

→ 모두 추상 메서드 1개
→ 람다 가능

2.3 같은 정신

같은 정신:

  RowMapper:
    - "ResultSet → 객체" 함수
    - 함수형
    - 람다로 전달

→ 3주차 정신

2.4 메서드 참조

// 메서드 참조 (3주차)
RowMapper<Shipment> mapper = ShipmentMapper::map;
// 또는
RowMapper<Shipment> mapper = this::mapRow;

private Shipment mapRow(ResultSet rs, int n) throws SQLException {
    Shipment s = new Shipment();
    s.setId(rs.getLong("id"));
    return s;
}
class Shipment { void setId(Long id) {} }
interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }
class ShipmentMapper { static Shipment map(ResultSet rs, int n) throws SQLException { return null; } }

2.5 ILIC 의 맥락

// 함수형 인터페이스 (ILIC)

// RowMapper 도 함수형 인터페이스
@FunctionalInterface
interface RowMapper<T> {
    T mapRow(ResultSet rs, int rowNum) throws SQLException;
}

// 3주차 함수형 인터페이스와 같은 정신
// Function<ResultSet, T> 와 유사 (예외 처리는 다름)

// ILIC 사용:
@Repository
public class ShipmentDao {
    private final JdbcTemplate jdbcTemplate;
    public ShipmentDao(JdbcTemplate jt) { this.jdbcTemplate = jt; }
    
    public List<Shipment> findAll() {
        return jdbcTemplate.query(
            "select * from shipments",
            // 람다 (3주차 정신)
            (rs, n) -> {
                Shipment s = new Shipment();
                s.setId(rs.getLong("id"));
                return s;
            }
        );
    }
}
class Shipment { void setId(Long id) {} }
class JdbcTemplate {
    <T> java.util.List<T> query(String s, RowMapper<T> r) { return null; }
}

2.6 자기 점검 답변

함수형 인터페이스 (3주차) 와의 연결은?

:
1. @FunctionalInterface:

  • 추상 1개
  1. 3주차 연결:

    • 같은 정신
  2. 람다 가능:

    • 핵심
  3. 메서드 참조:

    • 가능

3️⃣ 람다 표현

3.1 람다 표현

// 익명 클래스 (옛 방식)
RowMapper<Shipment> mapper1 = new RowMapper<Shipment>() {
    @Override
    public Shipment mapRow(ResultSet rs, int rowNum) throws SQLException {
        Shipment s = new Shipment();
        s.setId(rs.getLong("id"));
        return s;
    }
};

// 람다 (Java 8+, 권장)
RowMapper<Shipment> mapper2 = (rs, rowNum) -> {
    Shipment s = new Shipment();
    s.setId(rs.getLong("id"));
    return s;
};
class Shipment { void setId(Long id) {} }
interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }

3.2 inline 전달

// 호출 시 inline
List<Shipment> list = jdbcTemplate.query(
    "select * from shipments",
    (rs, n) -> {            // ← 람다 inline
        Shipment s = new Shipment();
        s.setId(rs.getLong("id"));
        return s;
    }
);
class Shipment { void setId(Long id) {} }
JdbcTemplate jdbcTemplate;
class JdbcTemplate {
    <T> java.util.List<T> query(String s, RowMapper<T> r) { return null; }
}
interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }

3.3 짧을 때

// 매핑이 짧으면 한 줄
List<String> blNos = jdbcTemplate.query(
    "select bl_no from shipments",
    (rs, n) -> rs.getString("bl_no")
);
// 또는
List<Long> ids = jdbcTemplate.query(
    "select id from shipments",
    (rs, n) -> rs.getLong("id")
);
JdbcTemplate jdbcTemplate;
class JdbcTemplate {
    <T> java.util.List<T> query(String s, RowMapper<T> r) { return null; }
}
interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }

3.4 람다의 가치

람다의 가치:

  - 코드 줄 ↓
  - 의도 명확 (변환 함수)
  - 가독성 ↑
  - 함수형 스타일

→ 3주차 학습 활용

3.5 ILIC 의 맥락

// 람다 표현 (ILIC)
@Repository
public class ShipmentDao {
    private final JdbcTemplate jdbcTemplate;
    public ShipmentDao(JdbcTemplate jt) { this.jdbcTemplate = jt; }
    
    public List<Shipment> findAll() {
        return jdbcTemplate.query(
            "select id, bl_no, weight, status from shipments",
            // 람다 inline
            (rs, n) -> {
                Shipment s = new Shipment();
                s.setId(rs.getLong("id"));
                s.setBlNo(rs.getString("bl_no"));
                s.setWeight(rs.getBigDecimal("weight"));
                s.setStatus(rs.getString("status"));
                return s;
            }
        );
    }
    
    // 짧은 매핑
    public List<String> findAllBlNos() {
        return jdbcTemplate.query(
            "select bl_no from shipments",
            (rs, n) -> rs.getString("bl_no")
        );
    }
    
    // 메서드 참조 (재사용)
    public List<Shipment> findByStatus(String status) {
        return jdbcTemplate.query(
            "select * from shipments where status = ?",
            this::mapShipment,
            status
        );
    }
    
    private Shipment mapShipment(ResultSet rs, int n) throws SQLException {
        Shipment s = new Shipment();
        s.setId(rs.getLong("id"));
        return s;
    }
}
class Shipment {
    void setId(Long id) {}
    void setBlNo(String s) {}
    void setWeight(java.math.BigDecimal w) {}
    void setStatus(String s) {}
}
class JdbcTemplate {
    <T> java.util.List<T> query(String s, RowMapper<T> r, Object... a) { return null; }
}
interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }

3.6 자기 점검 답변

(rs, rowNum) → 람다 표현은?

:
1. 람다:

  • (rs, n) -> { ... }
  1. inline:

    • 호출 시
  2. 짧을 때:

    • 한 줄
  3. 메서드 참조:

    • 재사용

4️⃣ BeanPropertyRowMapper

4.1 BeanPropertyRowMapper

BeanPropertyRowMapper:

  Spring 제공 매퍼:
    - 컬럼명 ↔ 자바 필드 자동
    - 매퍼 작성 X
    - 자동 매핑

4.2 사용

// BeanPropertyRowMapper
RowMapper<Shipment> mapper = new BeanPropertyRowMapper<>(Shipment.class);

List<Shipment> list = jdbcTemplate.query(
    "select id, bl_no, weight, status from shipments",
    mapper);
// 자동으로:
// id → setId(Long)
// bl_no → setBlNo(String) (snake → camel)
// weight → setWeight(BigDecimal)
// status → setStatus(String)

// 또는 짧게
List<Shipment> list2 = jdbcTemplate.query(
    "select id, bl_no, weight, status from shipments",
    BeanPropertyRowMapper.newInstance(Shipment.class));
class Shipment {}
class BeanPropertyRowMapper<T> implements RowMapper<T> {
    BeanPropertyRowMapper(Class<T> c) {}
    static <T> BeanPropertyRowMapper<T> newInstance(Class<T> c) { return null; }
    public T mapRow(ResultSet rs, int n) throws SQLException { return null; }
}
interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }
JdbcTemplate jdbcTemplate;
class JdbcTemplate {
    <T> java.util.List<T> query(String s, RowMapper<T> r) { return null; }
}

4.3 동작 원리

동작 원리:

  1. 클래스의 setter 메서드 분석
  2. 컬럼명 ↔ setter 이름 매칭
  3. snake_case ↔ camelCase 자동
  4. 타입 변환 (기본 + Date 등)
  5. setter 호출

4.4 장점

장점:

  - 매퍼 작성 X (자동)
  - 컬럼 추가 시 setter 만
  - 간결

4.5 단점

단점:

  - 컬럼명 ↔ 필드명 일치 필요
    - 다르면 매핑 X
  - 리플렉션 (약간 느림)
  - 복잡 매핑 어려움 (커스텀 변환)

→ 단순 매핑에 적합

4.6 ILIC 의 맥락

// BeanPropertyRowMapper (ILIC, 단순 케이스)

// 엔티티 (setter 필요)
public class Shipment {
    private Long id;
    private String blNo;          // bl_no 매핑
    private BigDecimal weight;
    private String status;
    private LocalDateTime createdAt;   // created_at 매핑
    
    // getter/setter (Lombok)
    // ...
}

// 매퍼 (자동)
@Repository
public class ShipmentDao {
    private final JdbcTemplate jdbcTemplate;
    private final RowMapper<Shipment> mapper = 
        BeanPropertyRowMapper.newInstance(Shipment.class);
    
    public ShipmentDao(JdbcTemplate jt) { this.jdbcTemplate = jt; }
    
    public List<Shipment> findAll() {
        return jdbcTemplate.query(
            "select id, bl_no, weight, status, created_at from shipments",
            mapper);
        // 자동 매핑
    }
}
class JdbcTemplate {
    <T> java.util.List<T> query(String s, RowMapper<T> r) { return null; }
}
interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }
class BeanPropertyRowMapper<T> implements RowMapper<T> {
    static <T> BeanPropertyRowMapper<T> newInstance(Class<T> c) { return null; }
    public T mapRow(ResultSet rs, int n) throws SQLException { return null; }
}

4.7 자기 점검 답변

BeanPropertyRowMapper 동작은?

:
1. BeanPropertyRowMapper:

  • 자동 매핑
  1. 동작:

    • setter 이용
  2. 장점:

    • 매퍼 X
  3. 단점:

    • 이름 일치 필요

5️⃣ snake_case → camelCase

5.1 명명 차이

명명 차이:

DB 컬럼:
  - snake_case (소문자_언더스코어)
  - bl_no, created_at, shipment_id

자바 필드:
  - camelCase (소문자카멜)
  - blNo, createdAt, shipmentId

→ 명명 컨벤션 다름

5.2 자동 변환

자동 변환:

  BeanPropertyRowMapper:
    - DB 컬럼 → 자바 필드
    - snake → camel
    - 자동

  매핑:
    bl_no → blNo
    created_at → createdAt
    shipment_id → shipmentId

5.3 대문자 / 소문자

대소문자:

  DB: 대부분 소문자 (관습)
  자바: 첫 글자 소문자, 단어 시작 대문자

  BeanPropertyRowMapper:
    - 대소문자 무관
    - underscore 처리

5.4 한계

한계:

  복잡한 컬럼명:
    - 정확히 매핑 X
    - 또는 다른 명명

  해결:
    - @Column 어노테이션 (JPA 만)
    - 또는 직접 RowMapper 작성

5.5 ILIC 의 맥락

// snake_case → camelCase (ILIC)

// DB 테이블
// CREATE TABLE shipments (
//     id BIGINT,
//     bl_no VARCHAR(50),
//     shipper_id BIGINT,
//     consignee_name VARCHAR(100),
//     created_at DATETIME,
//     updated_at DATETIME
// );

// 자바 클래스 (camelCase)
public class Shipment {
    private Long id;
    private String blNo;          // bl_no
    private Long shipperId;       // shipper_id
    private String consigneeName; // consignee_name
    private LocalDateTime createdAt;   // created_at
    private LocalDateTime updatedAt;   // updated_at
    
    // setter 생략 (Lombok @Setter)
}

// BeanPropertyRowMapper 가 자동
RowMapper<Shipment> mapper = 
    BeanPropertyRowMapper.newInstance(Shipment.class);

// 매핑:
// rs.getLong("id")            → setId
// rs.getString("bl_no")       → setBlNo
// rs.getLong("shipper_id")    → setShipperId
// rs.getString("consignee_name") → setConsigneeName
// rs.getTimestamp("created_at") → setCreatedAt
// rs.getTimestamp("updated_at") → setUpdatedAt
// 모두 자동
interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }
class BeanPropertyRowMapper<T> implements RowMapper<T> {
    static <T> BeanPropertyRowMapper<T> newInstance(Class<T> c) { return null; }
    public T mapRow(ResultSet rs, int n) throws SQLException { return null; }
}

5.6 자기 점검 답변

snake_case → camelCase 변환은?

:
1. 명명 차이:

  • DB snake, 자바 camel
  1. 자동:

    • BeanPropertyRowMapper
  2. 변환:

    • underscore 처리
  3. 한계:

    • 정확 매핑 필요

6️⃣ 5주차 전략 패턴 적용

6.1 전략 패턴

전략 패턴 (5주차):

  컨텍스트:
    - 변하지 않는 흐름

  전략 (인터페이스):
    - 변하는 부분
    - 외부 주입

→ 유연

6.2 RowMapper = 전략

RowMapper = 전략:

JdbcTemplate (컨텍스트):
  - 변하지 않는 흐름 (자원 관리)

RowMapper (전략):
  - 변하는 매핑
  - 호출 시 주입 (다른 매퍼)

→ 5주차 전략 패턴

6.3 다양한 전략

다양한 전략 (구현):

  - 직접 작성 람다
  - 메서드 참조
  - BeanPropertyRowMapper (Spring 제공)
  - DataClassRowMapper (record/POJO)
  - 커스텀 매퍼

→ 인터페이스 → N 구현

6.4 런타임 선택

// 런타임에 매퍼 선택
RowMapper<Shipment> mapper;
if (useAutoMapping) {
    mapper = BeanPropertyRowMapper.newInstance(Shipment.class);
} else {
    mapper = (rs, n) -> new Shipment();
}

List<Shipment> list = jdbcTemplate.query(sql, mapper);
// 같은 query, 다른 매핑 전략
class Shipment {}
class BeanPropertyRowMapper<T> implements RowMapper<T> {
    static <T> BeanPropertyRowMapper<T> newInstance(Class<T> c) { return null; }
    public T mapRow(ResultSet rs, int n) throws SQLException { return null; }
}
interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }
boolean useAutoMapping;
String sql;
JdbcTemplate jdbcTemplate;
class JdbcTemplate {
    <T> java.util.List<T> query(String s, RowMapper<T> r) { return null; }
}

6.5 ILIC 의 맥락

// 전략 패턴 (ILIC)
@Repository
public class ShipmentDao {
    private final JdbcTemplate jdbcTemplate;
    public ShipmentDao(JdbcTemplate jt) { this.jdbcTemplate = jt; }
    
    // 매퍼 (전략) 들
    private final RowMapper<Shipment> fullMapper = (rs, n) -> {
        Shipment s = new Shipment();
        s.setId(rs.getLong("id"));
        s.setBlNo(rs.getString("bl_no"));
        s.setWeight(rs.getBigDecimal("weight"));
        // 모든 필드
        return s;
    };
    
    private final RowMapper<Shipment> summaryMapper = (rs, n) -> {
        Shipment s = new Shipment();
        s.setId(rs.getLong("id"));
        s.setBlNo(rs.getString("bl_no"));
        // 일부 필드만 (간략)
        return s;
    };
    
    public List<Shipment> findAllFull() {
        return jdbcTemplate.query("select * from shipments", fullMapper);
    }
    
    public List<Shipment> findAllSummary() {
        return jdbcTemplate.query(
            "select id, bl_no from shipments", summaryMapper);
    }
    
    // 같은 JdbcTemplate, 다른 전략 (RowMapper)
    // → 5주차 전략 패턴 실제
}
class Shipment {
    void setId(Long id) {}
    void setBlNo(String s) {}
    void setWeight(java.math.BigDecimal w) {}
}
class JdbcTemplate {
    <T> java.util.List<T> query(String s, RowMapper<T> r) { return null; }
}
interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }

6.6 자기 점검 답변

5주차 전략 패턴 적용은?

:
1. 전략 패턴:

  • 5주차
  1. RowMapper:

    • 전략
  2. 다양한 구현:

    • 람다/BeanProperty/커스텀
  3. 런타임 선택:

    • 가능

7️⃣ 매퍼 재사용

7.1 매퍼 재사용

// 한 매퍼, 여러 쿼리
private final RowMapper<Shipment> mapper = (rs, n) -> {
    Shipment s = new Shipment();
    s.setId(rs.getLong("id"));
    s.setBlNo(rs.getString("bl_no"));
    return s;
};

public List<Shipment> findAll() {
    return jdbcTemplate.query("select * from shipments", mapper);
}

public Shipment findById(Long id) {
    return jdbcTemplate.queryForObject(
        "select * from shipments where id = ?", mapper, id);
}

public List<Shipment> findByStatus(String status) {
    return jdbcTemplate.query(
        "select * from shipments where status = ?", mapper, status);
}
// 한 매퍼 재사용
class Shipment {
    void setId(Long id) {}
    void setBlNo(String s) {}
}
interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }
JdbcTemplate jdbcTemplate;
class JdbcTemplate {
    <T> java.util.List<T> query(String s, RowMapper<T> r, Object... a) { return null; }
    <T> T queryForObject(String s, RowMapper<T> r, Object... a) { return null; }
}

7.2 static 필드

// static 으로 (불변 매퍼)
public class ShipmentDao {
    private static final RowMapper<Shipment> MAPPER = (rs, n) -> {
        Shipment s = new Shipment();
        s.setId(rs.getLong("id"));
        return s;
    };
    
    private final JdbcTemplate jdbcTemplate;
    public ShipmentDao(JdbcTemplate jt) { this.jdbcTemplate = jt; }
    
    // MAPPER 재사용
}
class Shipment { void setId(Long id) {} }
class JdbcTemplate {}
interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }

7.3 별도 클래스

// 별도 매퍼 클래스 (복잡 시)
public class ShipmentRowMapper implements RowMapper<Shipment> {
    @Override
    public Shipment mapRow(ResultSet rs, int n) throws SQLException {
        Shipment s = new Shipment();
        s.setId(rs.getLong("id"));
        s.setBlNo(rs.getString("bl_no"));
        // ... 복잡한 매핑
        return s;
    }
}

// 사용
List<Shipment> list = jdbcTemplate.query(sql, new ShipmentRowMapper());
class Shipment {
    void setId(Long id) {}
    void setBlNo(String s) {}
}
interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }
String sql;
JdbcTemplate jdbcTemplate;
class JdbcTemplate {
    <T> java.util.List<T> query(String s, RowMapper<T> r) { return null; }
}

7.4 ILIC 의 맥락

// 매퍼 재사용 (ILIC)
@Repository
public class ShipmentDao {
    // static 매퍼 (재사용)
    private static final RowMapper<Shipment> SHIPMENT_MAPPER = (rs, n) -> {
        Shipment s = new Shipment();
        s.setId(rs.getLong("id"));
        s.setBlNo(rs.getString("bl_no"));
        s.setWeight(rs.getBigDecimal("weight"));
        s.setStatus(rs.getString("status"));
        return s;
    };
    
    private final JdbcTemplate jdbcTemplate;
    public ShipmentDao(JdbcTemplate jt) { this.jdbcTemplate = jt; }
    
    // 여러 메서드가 같은 매퍼 사용
    public List<Shipment> findAll() {
        return jdbcTemplate.query("select * from shipments", SHIPMENT_MAPPER);
    }
    
    public Shipment findById(Long id) {
        return jdbcTemplate.queryForObject(
            "select * from shipments where id = ?", SHIPMENT_MAPPER, id);
    }
    
    public List<Shipment> findByStatus(String status) {
        return jdbcTemplate.query(
            "select * from shipments where status = ?", SHIPMENT_MAPPER, status);
    }
    
    // → 매핑 한 곳, 쿼리 다양
}
class Shipment {
    void setId(Long id) {}
    void setBlNo(String s) {}
    void setWeight(java.math.BigDecimal w) {}
    void setStatus(String s) {}
}
class JdbcTemplate {
    <T> java.util.List<T> query(String s, RowMapper<T> r, Object... a) { return null; }
    <T> T queryForObject(String s, RowMapper<T> r, Object... a) { return null; }
}
interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }

7.5 자기 점검 답변

매퍼 재사용 방법은?

:
1. 재사용:

  • 필드 / static
  1. static final:

    • 불변
  2. 별도 클래스:

    • 복잡 시
  3. 이점:

    • DRY

8️⃣ ResultSetExtractor 차이

8.1 RowMapper vs ResultSetExtractor

RowMapper:
  - 한 행 → 한 객체
  - JdbcTemplate 가 호출 반복
  - 보통 사용

ResultSetExtractor:
  - ResultSet 전체 → 임의 결과
  - 사용자가 ResultSet 직접
  - 특수 케이스

8.2 ResultSetExtractor 사용

// ResultSetExtractor (복잡 매핑)
List<Shipment> result = jdbcTemplate.query(
    "select * from shipments",
    (ResultSetExtractor<List<Shipment>>) rs -> {
        List<Shipment> shipments = new ArrayList<>();
        while (rs.next()) {
            Shipment s = new Shipment();
            s.setId(rs.getLong("id"));
            shipments.add(s);
        }
        return shipments;
    }
);
// ResultSet 직접 제어
class Shipment { void setId(Long id) {} }
interface ResultSetExtractor<T> { T extractData(ResultSet rs) throws SQLException; }
JdbcTemplate jdbcTemplate;
class JdbcTemplate {
    <T> T query(String s, ResultSetExtractor<T> e) { return null; }
}

8.3 언제 사용

ResultSetExtractor:

  - 1:N 매핑 (조인)
  - 그룹핑
  - 누적 (Map 결과)
  - 행 사이 관계

  RowMapper 로 어려운 케이스

8.4 비교

항목RowMapperResultSetExtractor
단위전체 ResultSet
호출행마다한 번
결과객체 (List 모음)임의
용도단순 매핑복잡/그룹핑

8.5 ILIC 의 맥락

// ResultSetExtractor (ILIC, 1:N 매핑)
@Repository
public class ShipmentWithDetailsDao {
    private final JdbcTemplate jdbcTemplate;
    public ShipmentWithDetailsDao(JdbcTemplate jt) { this.jdbcTemplate = jt; }
    
    // 한 Shipment + 여러 Detail (1:N)
    public Map<Long, List<Detail>> findShipmentDetails() {
        return jdbcTemplate.query(
            """
            select s.id as shipment_id, d.id as detail_id, d.item_name
            from shipments s
            join shipment_details d on s.id = d.shipment_id
            """,
            (ResultSetExtractor<Map<Long, List<Detail>>>) rs -> {
                Map<Long, List<Detail>> result = new HashMap<>();
                while (rs.next()) {
                    Long shipmentId = rs.getLong("shipment_id");
                    Detail d = new Detail();
                    d.setId(rs.getLong("detail_id"));
                    d.setItemName(rs.getString("item_name"));
                    
                    result.computeIfAbsent(shipmentId, k -> new ArrayList<>())
                          .add(d);
                }
                return result;
            }
        );
        // RowMapper 로는 1:N 매핑 어려움
        // ResultSetExtractor 로 그룹핑
    }
}
class Detail {
    void setId(Long id) {}
    void setItemName(String s) {}
}
class JdbcTemplate {
    <T> T query(String s, ResultSetExtractor<T> e) { return null; }
}
interface ResultSetExtractor<T> { T extractData(ResultSet rs) throws SQLException; }

8.6 자기 점검 답변

RowMapper vs ResultSetExtractor 차이는?

:
1. RowMapper:

  • 행마다
  1. ResultSetExtractor:

    • 전체 한 번
  2. 비교:

    • 단순 vs 복잡
  3. 활용:

    • 1:N, 그룹핑

9️⃣ 매핑 = 변하는 부분

9.1 매핑의 위치

매핑의 위치:

  변하지 않는 (JdbcTemplate):
    - Connection, Statement
    - 자원 관리

  변하는 (개발자):
    - SQL
    - 파라미터
    - 매핑 (RowMapper) ← 여기

9.2 매핑의 다양성

매핑의 다양성:

  같은 SQL 다른 매핑:
    - 전체 객체
    - 요약 객체
    - 단일 컬럼

  → 매퍼만 다르게

9.3 분리의 가치

분리의 가치:

  매핑이 별도 (전략):
    - 재사용
    - 테스트
    - 교체

→ 5주차 정신

9.4 함수형 + 전략

함수형 + 전략:

  RowMapper = 함수형 인터페이스 + 전략 패턴:
    - 3주차 함수형
    - 5주차 전략

→ Spring 의 정신

9.5 ILIC 의 맥락

// 매핑 = 변하는 부분 (ILIC)
@Repository
public class ShipmentDao {
    private final JdbcTemplate jdbcTemplate;
    public ShipmentDao(JdbcTemplate jt) { this.jdbcTemplate = jt; }
    
    // 같은 SQL, 다른 매핑 (전략)
    
    // 매핑 1: 전체 객체
    public List<Shipment> findAll() {
        return jdbcTemplate.query(
            "select * from shipments",
            (rs, n) -> {
                Shipment s = new Shipment();
                s.setId(rs.getLong("id"));
                s.setBlNo(rs.getString("bl_no"));
                s.setWeight(rs.getBigDecimal("weight"));
                // ... 모든 필드
                return s;
            }
        );
    }
    
    // 매핑 2: 요약 객체
    public List<ShipmentSummary> findAllSummary() {
        return jdbcTemplate.query(
            "select id, bl_no, status from shipments",
            (rs, n) -> {
                ShipmentSummary s = new ShipmentSummary();
                s.setId(rs.getLong("id"));
                s.setBlNo(rs.getString("bl_no"));
                s.setStatus(rs.getString("status"));
                return s;
            }
        );
    }
    
    // 매핑 3: 단일 컬럼
    public List<String> findAllBlNos() {
        return jdbcTemplate.query(
            "select bl_no from shipments",
            (rs, n) -> rs.getString("bl_no")
        );
    }
    
    // 같은 JdbcTemplate (흐름), 다른 매핑 (전략)
    // → 5주차 전략 패턴의 가장 명확한 사례
}
class Shipment {
    void setId(Long id) {}
    void setBlNo(String s) {}
    void setWeight(java.math.BigDecimal w) {}
}
class ShipmentSummary {
    void setId(Long id) {}
    void setBlNo(String s) {}
    void setStatus(String s) {}
}
class JdbcTemplate {
    <T> java.util.List<T> query(String s, RowMapper<T> r) { return null; }
}
interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }

9.6 면접 단골 질문 매핑

Q핵심 답변
RowMapper?행 → 객체
함수형?@FunctionalInterface
람다?(rs, n) -> ...
BeanProperty?snake → camel 자동
5주차 패턴?전략
재사용?static final
ResultSetExtractor?전체 ResultSet
매핑 = ?변하는 부분
3주차 연결?함수형/람다
다양한 매퍼?람다/BeanProperty/커스텀

9.7 자기 점검 체크리스트

정의

  • RowMapper

함수형

  • 3주차

람다

  • 표현

BeanProperty

  • 자동

snake/camel

  • 변환

전략 패턴

  • 5주차

재사용

  • 매퍼

vs Extractor

  • 차이

9.8 추가 심화 질문

Q1: DataClassRowMapper?

답:

  • Spring 5+ 추가
  • record / 불변 객체 매핑
  • 생성자 사용 (setter X)
  • 더 안전 (불변)

Q2: ColumnMapRowMapper?

답:

  • 행 → Map<String, Object>
  • 컬럼명: 값
  • 동적 컬럼 시
  • 타입 안전 X

Q3: 매핑 시 타입 변환?

답:

  • BeanPropertyRowMapper 가 자동
  • 기본 타입/Date/BigDecimal
  • 커스텀은 Converter

Q4: NULL 처리?

답:

  • ResultSet.wasNull()
  • getInt 등은 0 반환 시 wasNull 확인
  • getObject(String, Class) 더 안전 (Java 8+)

Q5: 매퍼와 트랜잭션?

답:

  • 매퍼 자체는 트랜잭션 X
  • JdbcTemplate 의 query 시 트랜잭션 인식
  • @Transactional 영향

🎯 핵심 요약 — 3줄 정리

1. RowMapper

  • ResultSet 한 행 → 자바 객체 매핑하는 함수형 인터페이스
  • (rs, rowNum) → 객체 람다로 표현 (3주차 람다 활용)

2. BeanPropertyRowMapper

  • snake_case 컬럼 → camelCase 필드 자동 매핑
  • 매퍼 작성 없이 setter 만으로 (간결)

3. 5주차 전략 패턴의 대표

  • JdbcTemplate (흐름) + RowMapper (매핑 전략) 의 결합
  • 같은 SQL, 다른 매퍼 가능 (전략 교체)
  • 3주차 함수형 + 5주차 전략 패턴 = Spring 의 정신

📚 다음으로...

Unit 7.5 — JdbcTemplate 의 구조적 의미 (Phase 7 완주 + 6주차 전체 완주!)

이번 Unit에서 RowMapper 를 봤다면, 다음은 Phase 7 마지막 (★ 6주차 전체 완주).

  • JdbcTemplate = 5주차 디자인 패턴의 집약
  • 관심사 분리 / 템플릿 메소드 / 전략 / OCP / DI / 람다
  • 종합 졸업 시험 25문항 (Part A 9 + JDBC 4 + DataSource 3 + ACID 5 + JdbcTemplate 4)
  • 6주차 전체 정리

Phase 7 진행 상황

🛠️ Phase 7 — JdbcTemplate
  ✅ Unit 7.1 JDBC 반복 코드
  ✅ Unit 7.2 JdbcTemplate 등장 ★깊이
  ✅ Unit 7.3 update/queryForObject/query
  ✅ Unit 7.4 RowMapper ← 여기
  ⏭ Unit 7.5 JdbcTemplate 구조적 의미 — Phase 7 + 6주차 완주!

6주차 누적 진행

🧪 Part A (9) ✅
💾 Part B
  ✅ Phase 3 (3)
  ✅ Phase 4 (4)
  ✅ Phase 5 (4)
  ✅ Phase 6 (6)
  🛠️ Phase 7 (4/5)

총: 30/31 Unit (96%!)

F-LAB JAVA · 6주차 · Phase 7 · Unit 7.4 · 끝

profile
Software Developer

0개의 댓글