F-LAB JAVA · 6주차 · Phase 7 · JdbcTemplate (반복 제거)
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
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 등 다양한 방식으로 전달할 수 있다.
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 자동, 전략 패턴.
1. RowMapper 정의
2. 함수형 인터페이스 (3주차 연결)
3. 람다 표현
4. BeanPropertyRowMapper
5. snake_case → camelCase
6. 5주차 전략 패턴 적용
7. 매퍼 재사용
8. ResultSetExtractor 차이
9. 매핑 = 변하는 부분
RowMapper:
ResultSet 의 한 행을:
- 자바 객체로 매핑
- 함수형 인터페이스
org.springframework.jdbc.core.RowMapper
// RowMapper 인터페이스 (Spring)
@FunctionalInterface
public interface RowMapper<T> {
T mapRow(ResultSet rs, int rowNum) throws SQLException;
}
// 단 하나의 추상 메서드
// → 람다 가능
mapRow(rs, rowNum):
rs: 현재 행 가리키는 ResultSet
- rs.getLong("id")
- rs.getString("bl_no")
- 등
rowNum: 행 번호 (0부터)
- 잘 안 씀
- 매핑에 행 번호 필요 시
반환:
T: 매핑된 객체
- Shipment, Booking 등
- 한 행 → 한 객체
→ 변환 결과
// 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; }
RowMapper 의 정의는?
답:
1. RowMapper:
인터페이스:
메서드:
반환:
RowMapper = 함수형 인터페이스:
@FunctionalInterface:
- 추상 메서드 단 하나
- 람다 가능
- Java 8+
3주차 함수형 인터페이스:
- Function<T, R>
- Predicate<T>
- Consumer<T>
- Supplier<T>
→ 모두 추상 메서드 1개
→ 람다 가능
같은 정신:
RowMapper:
- "ResultSet → 객체" 함수
- 함수형
- 람다로 전달
→ 3주차 정신
// 메서드 참조 (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; } }
// 함수형 인터페이스 (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; }
}
함수형 인터페이스 (3주차) 와의 연결은?
답:
1. @FunctionalInterface:
3주차 연결:
람다 가능:
메서드 참조:
// 익명 클래스 (옛 방식)
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; }
// 호출 시 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; }
// 매핑이 짧으면 한 줄
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주차 학습 활용
// 람다 표현 (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; }
(rs, rowNum) → 람다 표현은?
답:
1. 람다:
inline:
짧을 때:
메서드 참조:
BeanPropertyRowMapper:
Spring 제공 매퍼:
- 컬럼명 ↔ 자바 필드 자동
- 매퍼 작성 X
- 자동 매핑
// 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; }
}
동작 원리:
1. 클래스의 setter 메서드 분석
2. 컬럼명 ↔ setter 이름 매칭
3. snake_case ↔ camelCase 자동
4. 타입 변환 (기본 + Date 등)
5. setter 호출
장점:
- 매퍼 작성 X (자동)
- 컬럼 추가 시 setter 만
- 간결
단점:
- 컬럼명 ↔ 필드명 일치 필요
- 다르면 매핑 X
- 리플렉션 (약간 느림)
- 복잡 매핑 어려움 (커스텀 변환)
→ 단순 매핑에 적합
// 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; }
}
BeanPropertyRowMapper 동작은?
답:
1. BeanPropertyRowMapper:
동작:
장점:
단점:
명명 차이:
DB 컬럼:
- snake_case (소문자_언더스코어)
- bl_no, created_at, shipment_id
자바 필드:
- camelCase (소문자카멜)
- blNo, createdAt, shipmentId
→ 명명 컨벤션 다름
자동 변환:
BeanPropertyRowMapper:
- DB 컬럼 → 자바 필드
- snake → camel
- 자동
매핑:
bl_no → blNo
created_at → createdAt
shipment_id → shipmentId
대소문자:
DB: 대부분 소문자 (관습)
자바: 첫 글자 소문자, 단어 시작 대문자
BeanPropertyRowMapper:
- 대소문자 무관
- underscore 처리
한계:
복잡한 컬럼명:
- 정확히 매핑 X
- 또는 다른 명명
해결:
- @Column 어노테이션 (JPA 만)
- 또는 직접 RowMapper 작성
// 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; }
}
snake_case → camelCase 변환은?
답:
1. 명명 차이:
자동:
변환:
한계:
전략 패턴 (5주차):
컨텍스트:
- 변하지 않는 흐름
전략 (인터페이스):
- 변하는 부분
- 외부 주입
→ 유연
RowMapper = 전략:
JdbcTemplate (컨텍스트):
- 변하지 않는 흐름 (자원 관리)
RowMapper (전략):
- 변하는 매핑
- 호출 시 주입 (다른 매퍼)
→ 5주차 전략 패턴
다양한 전략 (구현):
- 직접 작성 람다
- 메서드 참조
- BeanPropertyRowMapper (Spring 제공)
- DataClassRowMapper (record/POJO)
- 커스텀 매퍼
→ 인터페이스 → N 구현
// 런타임에 매퍼 선택
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; }
}
// 전략 패턴 (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; }
5주차 전략 패턴 적용은?
답:
1. 전략 패턴:
RowMapper:
다양한 구현:
런타임 선택:
// 한 매퍼, 여러 쿼리
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; }
}
// 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; }
// 별도 매퍼 클래스 (복잡 시)
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; }
}
// 매퍼 재사용 (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; }
매퍼 재사용 방법은?
답:
1. 재사용:
static final:
별도 클래스:
이점:
RowMapper:
- 한 행 → 한 객체
- JdbcTemplate 가 호출 반복
- 보통 사용
ResultSetExtractor:
- ResultSet 전체 → 임의 결과
- 사용자가 ResultSet 직접
- 특수 케이스
// 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; }
}
ResultSetExtractor:
- 1:N 매핑 (조인)
- 그룹핑
- 누적 (Map 결과)
- 행 사이 관계
RowMapper 로 어려운 케이스
| 항목 | RowMapper | ResultSetExtractor |
|---|---|---|
| 단위 | 행 | 전체 ResultSet |
| 호출 | 행마다 | 한 번 |
| 결과 | 객체 (List 모음) | 임의 |
| 용도 | 단순 매핑 | 복잡/그룹핑 |
// 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; }
RowMapper vs ResultSetExtractor 차이는?
답:
1. RowMapper:
ResultSetExtractor:
비교:
활용:
매핑의 위치:
변하지 않는 (JdbcTemplate):
- Connection, Statement
- 자원 관리
변하는 (개발자):
- SQL
- 파라미터
- 매핑 (RowMapper) ← 여기
매핑의 다양성:
같은 SQL 다른 매핑:
- 전체 객체
- 요약 객체
- 단일 컬럼
→ 매퍼만 다르게
분리의 가치:
매핑이 별도 (전략):
- 재사용
- 테스트
- 교체
→ 5주차 정신
함수형 + 전략:
RowMapper = 함수형 인터페이스 + 전략 패턴:
- 3주차 함수형
- 5주차 전략
→ Spring 의 정신
// 매핑 = 변하는 부분 (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; }
| Q | 핵심 답변 |
|---|---|
| RowMapper? | 행 → 객체 |
| 함수형? | @FunctionalInterface |
| 람다? | (rs, n) -> ... |
| BeanProperty? | snake → camel 자동 |
| 5주차 패턴? | 전략 |
| 재사용? | static final |
| ResultSetExtractor? | 전체 ResultSet |
| 매핑 = ? | 변하는 부분 |
| 3주차 연결? | 함수형/람다 |
| 다양한 매퍼? | 람다/BeanProperty/커스텀 |
답:
답:
답:
답:
답:
1. RowMapper
(rs, rowNum) → 객체 람다로 표현 (3주차 람다 활용)2. BeanPropertyRowMapper
3. 5주차 전략 패턴의 대표
이번 Unit에서 RowMapper 를 봤다면, 다음은 Phase 7 마지막 (★ 6주차 전체 완주).
🛠️ 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주차 완주!
🧪 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 · 끝