6주차 Unit 7.3 — update / queryForObject / query

Psj·2026년 6월 1일

F-lab

목록 보기
211/239

Unit 7.3 — update / queryForObject / query

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


📌 학습 목표

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

  • update(sql, params) 의 동작은?
  • queryForObject(sql, mapper, params) 의 동작은?
  • query(sql, mapper, params) 의 동작은?
  • execute(sql) 의 용도는?
  • queryForObject 결과 0건이면 ?
  • queryForObject 결과 2건 이상이면 ?
  • update 반환값 (영향받은 행 수) 은?
  • batchUpdate 는?
  • 각 메서드의 적절한 용도 는?

🎯 핵심 한 문장

JdbcTemplate 의 핵심 메서드는 update (INSERT/UPDATE/DELETE, 영향받은 행 수 반환), queryForObject (단일 행 조회, 객체 1개 반환), query (여러 행 조회, List 반환), execute (DDL 등 임의 SQL) 이며, queryForObject 는 결과 0건이면 EmptyResultDataAccessException, 2건 이상이면 IncorrectResultSizeDataAccessException 을 던진다.
JdbcTemplate 의 네 가지 핵심 메서드는 각자 분명한 역할이 있다.
update(sql, params) 는 INSERT/UPDATE/DELETE 에 사용하며 영향받은 행 수를 int 로 반환한다 — 0이면 변경 X (조건에 맞는 행 없음), N이면 N 개 변경.
queryForObject(sql, mapper, params) 는 결과가 정확히 1건 일 때 사용하며 객체 하나를 반환 — 0건이면 EmptyResultDataAccessException, 2건 이상이면 IncorrectResultSizeDataAccessException 을 던진다.
query(sql, mapper, params) 는 결과 여러 건일 때 사용해 List<T> 반환 (0건이면 빈 리스트), execute(sql) 는 DDL (CREATE/DROP) 등 결과 없는 임의 SQL 에 사용한다.

비유 — 자판기 버튼 4개

JdbcTemplate 메서드 = 자판기 버튼:

update (쓰기 버튼):
  - INSERT/UPDATE/DELETE
  - "데이터 변경"
  - 결과: 변경된 갯수 (int)

queryForObject (1개 조회 버튼):
  - "정확히 하나 주세요"
  - 0개? → 에러 (EmptyResult)
  - 2개+? → 에러 (IncorrectSize)
  - 1개? → 객체 반환

query (목록 조회 버튼):
  - "여러 개 주세요"
  - 0개? → 빈 리스트
  - N개? → List<T>

execute (특수 명령):
  - "임의 SQL 실행"
  - DDL (CREATE/DROP)
  - 결과 없음

자판기 안 (Template):
  - 어느 버튼이든
  - 자원 관리 자동
  - 예외 변환 자동
  - 변하는 것: 버튼별 동작

batchUpdate (한꺼번에):
  - 여러 SQL 묶음
  - 성능 ↑

→ 4가지 핵심 메서드 (update/queryForObject/query/execute) + batchUpdate, 각자 분명한 용도.


🧭 9개 섹션 로드맵

1. 핵심 메서드 4가지
2. update()
3. queryForObject()
4. query()
5. execute()
6. queryForObject 결과 0건
7. queryForObject 결과 N건
8. batchUpdate
9. 면접 + 자기 점검

1️⃣ 핵심 메서드 4가지

1.1 4가지

JdbcTemplate 4가지 핵심:

  update(sql, params)
    - INSERT/UPDATE/DELETE
    - 영향받은 행 수 (int)

  queryForObject(sql, mapper, params)
    - 단일 행 조회
    - 객체 1개

  query(sql, mapper, params)
    - 여러 행 조회
    - List<T>

  execute(sql)
    - 임의 SQL (DDL)

1.2 분명한 분리

분명한 분리:

  쓰기 (변경) → update
  단일 읽기 → queryForObject
  목록 읽기 → query
  기타 → execute

→ 메서드만 봐도 의도 명확

1.3 추가 메서드

추가 메서드:

  - batchUpdate (대량)
  - queryForList (List<Map>)
  - queryForMap (단일 Map)
  - queryForRowSet (RowSet)

→ 편의 메서드 다수

1.4 ILIC 의 맥락

// 4가지 메서드 (ILIC)
@Repository
public class ShipmentDao {
    private final JdbcTemplate jdbcTemplate;
    public ShipmentDao(JdbcTemplate jt) { this.jdbcTemplate = jt; }
    
    public void add(Shipment s) {
        // update (INSERT)
        jdbcTemplate.update("insert into shipments ...");
    }
    
    public Shipment get(Long id) {
        // queryForObject (단일)
        return jdbcTemplate.queryForObject(
            "select * from shipments where id = ?",
            mapper, id);
    }
    
    public List<Shipment> findAll() {
        // query (다수)
        return jdbcTemplate.query(
            "select * from shipments",
            mapper);
    }
    
    public void initSchema() {
        // execute (DDL)
        jdbcTemplate.execute("create table if not exists ...");
    }
    
    private final RowMapper<Shipment> mapper = (rs, n) -> {
        Shipment s = new Shipment();
        s.setId(rs.getLong("id"));
        return s;
    };
}
class Shipment { void setId(Long id) {} }
class JdbcTemplate {
    void update(String s, Object... a) {}
    <T> T queryForObject(String s, RowMapper<T> r, Object... a) { return null; }
    <T> java.util.List<T> query(String s, RowMapper<T> r) { return null; }
    void execute(String s) {}
    interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }
}

1.5 자기 점검 답변

핵심 메서드 4가지는?

:
1. update:

  • 쓰기
  1. queryForObject:

    • 단일
  2. query:

    • 목록
  3. execute:

    • 기타

2️⃣ update()

2.1 사용

// INSERT
int rows = jdbcTemplate.update(
    "insert into shipments (bl_no, weight) values (?, ?)",
    "BL001", new BigDecimal("100"));
// rows = 1 (1행 추가)

// UPDATE
int rows = jdbcTemplate.update(
    "update shipments set status = ? where id = ?",
    "SHIPPED", 1L);
// rows = 영향 받은 행 수

// DELETE
int rows = jdbcTemplate.update(
    "delete from shipments where id = ?",
    1L);
// rows = 삭제된 행 수

2.2 반환값

update() 반환:

  영향받은 행 수 (int):
    - 0: 조건에 맞는 행 없음
    - N: N개 변경

→ executeUpdate() 와 같음

2.3 활용

// 활용 (영향받은 행 수)
public boolean updateStatus(Long id, String status) {
    int rows = jdbcTemplate.update(
        "update shipments set status = ? where id = ?",
        status, id);
    return rows > 0;   // 변경됐는지
}
JdbcTemplate jdbcTemplate;
class JdbcTemplate { int update(String s, Object... a) { return 0; } }

2.4 자동 키 생성

// 자동 키 생성 (KeyHolder)
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
    PreparedStatement ps = connection.prepareStatement(
        "insert into shipments (bl_no) values (?)",
        Statement.RETURN_GENERATED_KEYS);
    ps.setString(1, "BL001");
    return ps;
}, keyHolder);

Long generatedId = keyHolder.getKey().longValue();
class KeyHolder { Number getKey() { return null; } }
class GeneratedKeyHolder extends KeyHolder {}
class Statement { static int RETURN_GENERATED_KEYS = 1; }

2.5 ILIC 의 맥락

// update() 활용 (ILIC)
@Repository
public class ShipmentDao {
    private final JdbcTemplate jdbcTemplate;
    public ShipmentDao(JdbcTemplate jt) { this.jdbcTemplate = jt; }
    
    public void add(Shipment s) {
        jdbcTemplate.update(
            "insert into shipments (bl_no, weight, status) values (?, ?, ?)",
            s.getBlNo(), s.getWeight(), s.getStatus());
    }
    
    public boolean updateStatus(Long id, String status) {
        int rows = jdbcTemplate.update(
            "update shipments set status = ? where id = ?",
            status, id);
        return rows > 0;   // 변경 여부
    }
    
    public int deleteOldShipments(LocalDate cutoff) {
        return jdbcTemplate.update(
            "delete from shipments where created_at < ?",
            cutoff);
        // 삭제된 행 수 반환 (운영 통계용)
    }
}
class Shipment {
    String getBlNo() { return null; }
    java.math.BigDecimal getWeight() { return null; }
    String getStatus() { return null; }
}
class JdbcTemplate { int update(String s, Object... a) { return 0; } }

2.6 자기 점검 답변

update(sql, params) 의 동작은?

:
1. 사용:

  • INSERT/UPDATE/DELETE
  1. 반환:

    • 영향 행 수 (int)
  2. 활용:

    • 변경 여부
  3. 자동 키:

    • KeyHolder

3️⃣ queryForObject()

3.1 사용

// queryForObject (단일 행)
Shipment s = jdbcTemplate.queryForObject(
    "select * from shipments where id = ?",
    (rs, n) -> {
        Shipment shp = new Shipment();
        shp.setId(rs.getLong("id"));
        return shp;
    },
    1L
);
// s = 객체 1개
class Shipment { void setId(Long id) {} }
JdbcTemplate jdbcTemplate;
class JdbcTemplate {
    <T> T queryForObject(String s, RowMapper<T> r, Object... a) { return null; }
    interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }
}

3.2 단일 값 (기본 타입)

// 단일 값 (count, sum 등)
int count = jdbcTemplate.queryForObject(
    "select count(*) from shipments",
    Integer.class);

BigDecimal total = jdbcTemplate.queryForObject(
    "select sum(weight) from shipments",
    BigDecimal.class);

String name = jdbcTemplate.queryForObject(
    "select bl_no from shipments where id = ?",
    String.class, 1L);
JdbcTemplate jdbcTemplate;
class JdbcTemplate {
    <T> T queryForObject(String s, Class<T> c, Object... a) { return null; }
}

3.3 결과 정확히 1건

결과 1건 가정:

  - 0건: 예외
  - 1건: 객체 반환
  - 2건 이상: 예외

→ 1건 보장된 곳에서만

3.4 1건 보장 예

1건 보장 예:

  - PK 로 조회: WHERE id = ?
  - UNIQUE 컬럼: WHERE bl_no = ?
  - 집계: COUNT(*), SUM(*)

→ 의미상 1건

3.5 ILIC 의 맥락

// queryForObject (ILIC)
@Repository
public class ShipmentDao {
    private final JdbcTemplate jdbcTemplate;
    public ShipmentDao(JdbcTemplate jt) { this.jdbcTemplate = jt; }
    
    // 객체 1개
    public Shipment get(Long id) {
        return jdbcTemplate.queryForObject(
            "select id, bl_no, weight from shipments where id = ?",
            (rs, n) -> {
                Shipment s = new Shipment();
                s.setId(rs.getLong("id"));
                return s;
            },
            id);
    }
    
    // 단일 값 (count)
    public int count() {
        return jdbcTemplate.queryForObject(
            "select count(*) from shipments",
            Integer.class);
    }
    
    // 집계
    public BigDecimal totalWeight() {
        return jdbcTemplate.queryForObject(
            "select sum(weight) from shipments where status = ?",
            BigDecimal.class, "SHIPPED");
    }
}
class Shipment { void setId(Long id) {} }
class JdbcTemplate {
    <T> T queryForObject(String s, RowMapper<T> r, Object... a) { return null; }
    <T> T queryForObject(String s, Class<T> c, Object... a) { return null; }
    interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }
}

3.6 자기 점검 답변

queryForObject(sql, mapper, params) 의 동작은?

:
1. 사용:

  • 단일 행
  1. 단일 값:

    • Class
  2. 1건 가정:

    • 0 / 2+ 예외
  3. 활용:

    • PK/UNIQUE/집계

4️⃣ query()

4.1 사용

// query (여러 행)
List<Shipment> list = jdbcTemplate.query(
    "select * from shipments where status = ?",
    (rs, n) -> {
        Shipment s = new Shipment();
        s.setId(rs.getLong("id"));
        return s;
    },
    "SHIPPED"
);
// list = 0개 이상
class Shipment { void setId(Long id) {} }
JdbcTemplate jdbcTemplate;
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; }
}

4.2 0건이면 빈 리스트

0건이면:

  - 예외 X
  - 빈 리스트 (List.of() 또는 ArrayList())
  - 안전

→ queryForObject 와 다른 점

4.3 RowMapper 재사용

// RowMapper 재사용
private final RowMapper<Shipment> shipmentMapper = (rs, n) -> {
    Shipment s = new Shipment();
    s.setId(rs.getLong("id"));
    s.setBlNo(rs.getString("bl_no"));
    return s;
};

public List<Shipment> findByStatus(String status) {
    return jdbcTemplate.query(
        "select * from shipments where status = ?",
        shipmentMapper, status);
}

public List<Shipment> findHeavy() {
    return jdbcTemplate.query(
        "select * from shipments where weight > 1000",
        shipmentMapper);
}
// 같은 매퍼, 다른 쿼리
class Shipment { void setId(Long id) {} void setBlNo(String s) {} }
JdbcTemplate jdbcTemplate;
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; }
}
interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }

4.4 queryForList

// queryForList (Map 으로)
List<Map<String, Object>> list = jdbcTemplate.queryForList(
    "select id, bl_no from shipments");
// 각 행이 Map (컬럼명 → 값)
// 매퍼 작성 불필요

// 한 컬럼 List
List<String> blNos = jdbcTemplate.queryForList(
    "select bl_no from shipments",
    String.class);
class JdbcTemplate {
    java.util.List<java.util.Map<String, Object>> queryForList(String s) { return null; }
    <T> java.util.List<T> queryForList(String s, Class<T> c) { return null; }
}
JdbcTemplate jdbcTemplate;

4.5 ILIC 의 맥락

// query() (ILIC)
@Repository
public class ShipmentDao {
    private final JdbcTemplate jdbcTemplate;
    public ShipmentDao(JdbcTemplate jt) { this.jdbcTemplate = jt; }
    
    private final RowMapper<Shipment> mapper = (rs, n) -> {
        Shipment s = new Shipment();
        s.setId(rs.getLong("id"));
        s.setBlNo(rs.getString("bl_no"));
        s.setStatus(rs.getString("status"));
        return s;
    };
    
    public List<Shipment> findAll() {
        return jdbcTemplate.query("select * from shipments", mapper);
        // 0건이면 빈 리스트
    }
    
    public List<Shipment> findByStatus(String status) {
        return jdbcTemplate.query(
            "select * from shipments where status = ?",
            mapper, status);
    }
    
    // queryForList (Map)
    public List<Map<String, Object>> statusSummary() {
        return jdbcTemplate.queryForList(
            "select status, count(*) as cnt from shipments group by status");
        // 각 행이 {status: ..., cnt: ...}
    }
    
    // 한 컬럼 List
    public List<String> distinctStatuses() {
        return jdbcTemplate.queryForList(
            "select distinct status from shipments",
            String.class);
    }
}
class Shipment {
    void setId(Long id) {}
    void setBlNo(String s) {}
    void setStatus(String s) {}
}
interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }
class JdbcTemplate {
    <T> java.util.List<T> query(String s, RowMapper<T> r, Object... a) { return null; }
    java.util.List<java.util.Map<String, Object>> queryForList(String s) { return null; }
    <T> java.util.List<T> queryForList(String s, Class<T> c) { return null; }
}

4.6 자기 점검 답변

query(sql, mapper, params) 의 동작은?

:
1. 사용:

  • 여러 행
  1. 반환:

    • List
  2. 0건:

    • 빈 리스트 (예외 X)
  3. 편의:

    • queryForList

5️⃣ execute()

5.1 execute()

execute():

  임의 SQL 실행:
    - 결과 처리 X
    - DDL (CREATE/DROP)
    - 특수 명령

5.2 DDL 예

// DDL (테이블 생성/삭제)
jdbcTemplate.execute("""
    create table if not exists shipments (
        id BIGINT AUTO_INCREMENT PRIMARY KEY,
        bl_no VARCHAR(50) UNIQUE,
        weight DECIMAL(10,2),
        status VARCHAR(20)
    )
""");

jdbcTemplate.execute("drop table if exists temp_shipments");
JdbcTemplate jdbcTemplate;
class JdbcTemplate { void execute(String s) {} }

5.3 특수 명령

// 특수 (truncate, set 등)
jdbcTemplate.execute("truncate table audit_logs");
jdbcTemplate.execute("set global max_connections = 200");

5.4 보통 안 씀

보통 안 씀:

  운영 코드에서:
    - 거의 update/query 만
    - DDL 은 마이그레이션 도구
    - Flyway, Liquibase

→ execute 는 특수 케이스

5.5 ILIC 의 맥락

// execute() (ILIC, 보통 안 씀)
@Configuration
@Profile("test")
class TestSchemaInitializer {
    @Autowired
    JdbcTemplate jdbcTemplate;
    
    @PostConstruct
    void initSchema() {
        // 테스트용 임시 테이블 (H2)
        jdbcTemplate.execute("""
            create table if not exists test_shipments (
                id BIGINT PRIMARY KEY,
                bl_no VARCHAR(50)
            )
        """);
    }
}

// 운영은 Flyway 등 사용
// → execute 는 거의 안 씀
class JdbcTemplate { void execute(String s) {} }

5.6 자기 점검 답변

execute(sql) 의 용도는?

:
1. execute:

  • 임의 SQL
  1. DDL:

    • CREATE/DROP
  2. 특수:

    • truncate/set
  3. 운영:

    • 거의 안 씀

6️⃣ queryForObject 결과 0건

6.1 EmptyResultDataAccessException

queryForObject 결과 0건:

  → EmptyResultDataAccessException

  - RuntimeException
  - "결과 0건인데 1건 기대"

6.2 처리

// 처리 패턴
public Shipment findById(Long id) {
    try {
        return jdbcTemplate.queryForObject(
            "select * from shipments where id = ?",
            mapper, id);
    } catch (EmptyResultDataAccessException e) {
        return null;   // 없으면 null
    }
}
class Shipment {}
JdbcTemplate jdbcTemplate;
RowMapper<Shipment> mapper;
class JdbcTemplate {
    <T> T queryForObject(String s, RowMapper<T> r, Object... a) { return null; }
}
interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }
class EmptyResultDataAccessException extends RuntimeException {}

6.3 Optional 활용

// Optional 활용 (더 좋음)
public Optional<Shipment> findById(Long id) {
    try {
        Shipment s = jdbcTemplate.queryForObject(
            "select * from shipments where id = ?",
            mapper, id);
        return Optional.ofNullable(s);
    } catch (EmptyResultDataAccessException e) {
        return Optional.empty();
    }
}
class Shipment {}
JdbcTemplate jdbcTemplate;
RowMapper<Shipment> mapper;
class JdbcTemplate {
    <T> T queryForObject(String s, RowMapper<T> r, Object... a) { return null; }
}
interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }
class EmptyResultDataAccessException extends RuntimeException {}

6.4 의도된 예외

의도된 예외:

  PK 로 조회:
    - 보통 1건 있음
    - 없으면 의외 (예외)

  존재 확인:
    - count(*) 가 더 적합
    - queryForObject 부적합

6.5 ILIC 의 맥락

// 0건 처리 (ILIC)
@Repository
public class ShipmentDao {
    private final JdbcTemplate jdbcTemplate;
    public ShipmentDao(JdbcTemplate jt) { this.jdbcTemplate = jt; }
    
    // 패턴 1: null 반환
    public Shipment findById(Long id) {
        try {
            return jdbcTemplate.queryForObject(
                "select * from shipments where id = ?",
                mapper, id);
        } catch (EmptyResultDataAccessException e) {
            return null;
        }
    }
    
    // 패턴 2: Optional (권장)
    public Optional<Shipment> findByIdOptional(Long id) {
        try {
            return Optional.ofNullable(
                jdbcTemplate.queryForObject(
                    "select * from shipments where id = ?",
                    mapper, id));
        } catch (EmptyResultDataAccessException e) {
            return Optional.empty();
        }
    }
    
    // 패턴 3: 예외 그대로 (도메인 예외)
    public Shipment getRequired(Long id) {
        try {
            return jdbcTemplate.queryForObject(
                "select * from shipments where id = ?",
                mapper, id);
        } catch (EmptyResultDataAccessException e) {
            throw new ShipmentNotFoundException(id);
        }
    }
    
    private final RowMapper<Shipment> mapper = (rs, n) -> new Shipment();
}
class Shipment {}
class ShipmentNotFoundException extends RuntimeException {
    ShipmentNotFoundException(Long id) {}
}
class JdbcTemplate {
    <T> T queryForObject(String s, RowMapper<T> r, Object... a) { return null; }
}
interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }
class EmptyResultDataAccessException extends RuntimeException {}

6.6 자기 점검 답변

queryForObject 결과 0건이면?

:
1. 0건:

  • EmptyResultDataAccessException
  1. 처리:

    • try/catch
  2. 권장:

    • Optional
  3. 대안:

    • 도메인 예외

7️⃣ queryForObject 결과 N건

7.1 IncorrectResultSizeDataAccessException

queryForObject 결과 2건 이상:

  → IncorrectResultSizeDataAccessException

  - 1건 기대, 실제 N건
  - 데이터 무결성 의심
  - 또는 쿼리 잘못

7.2 원인

원인:

  - WHERE 조건 부족 (UNIQUE 가정인데 중복)
  - 데이터 중복 (제약 누락)
  - 쿼리 실수

→ 코드 또는 데이터 문제

7.3 처리

// 보통 잡지 않음 (의외)
try {
    return jdbcTemplate.queryForObject(...);
} catch (IncorrectResultSizeDataAccessException e) {
    // 데이터 무결성 문제 발견
    log.error("Multiple rows for unique query: {}", e.getActualSize());
    throw new DataIntegrityException(e);
}
JdbcTemplate jdbcTemplate;
class JdbcTemplate {
    <T> T queryForObject(String s, RowMapper<T> r, Object... a) { return null; }
}
interface RowMapper<T> { T mapRow(ResultSet rs, int n); }
class IncorrectResultSizeDataAccessException extends RuntimeException { int getActualSize() { return 0; } }
class DataIntegrityException extends RuntimeException { DataIntegrityException(Throwable t) {} }
org.slf4j.Logger log;

7.4 예방

예방:

  - PK / UNIQUE 컬럼으로 조회
  - DB 제약 (UNIQUE) 사용
  - 쿼리 검토

→ 1건 보장 쿼리만

7.5 ILIC 의 맥락

// N건 발생 시나리오 (ILIC)
@Repository
public class ShipmentDao {
    private final JdbcTemplate jdbcTemplate;
    public ShipmentDao(JdbcTemplate jt) { this.jdbcTemplate = jt; }
    
    // 안전: PK 조회
    public Shipment findById(Long id) {
        return jdbcTemplate.queryForObject(
            "select * from shipments where id = ?",
            mapper, id);
        // PK 라 1건 보장
    }
    
    // 안전: UNIQUE 조회
    public Shipment findByBlNo(String blNo) {
        return jdbcTemplate.queryForObject(
            "select * from shipments where bl_no = ?",
            mapper, blNo);
        // bl_no UNIQUE 라 1건 보장
    }
    
    // ❌ 위험: 1건 보장 X
    public Shipment findByStatus(String status) {
        return jdbcTemplate.queryForObject(
            "select * from shipments where status = ?",
            mapper, status);
        // 여러 행 가능 → IncorrectResultSize
        // → query() 써야
    }
    
    private final RowMapper<Shipment> mapper = (rs, n) -> new Shipment();
}
class Shipment {}
class JdbcTemplate {
    <T> T queryForObject(String s, RowMapper<T> r, Object... a) { return null; }
}
interface RowMapper<T> { T mapRow(ResultSet rs, int n); }

7.6 자기 점검 답변

queryForObject 결과 2건 이상이면?

:
1. N건:

  • IncorrectResultSizeDataAccessException
  1. 원인:

    • 조건 부족/중복
  2. 처리:

    • 보통 의외 (예외 전파)
  3. 예방:

    • PK/UNIQUE 만

8️⃣ batchUpdate

8.1 batchUpdate

batchUpdate:

  여러 SQL 한꺼번에:
    - 네트워크 왕복 1회
    - 성능 ↑
    - 대량 처리

→ INSERT/UPDATE 다수

8.2 사용

// batchUpdate
List<Shipment> shipments = ...;

int[] rows = jdbcTemplate.batchUpdate(
    "insert into shipments (bl_no, weight) values (?, ?)",
    new BatchPreparedStatementSetter() {
        @Override
        public void setValues(PreparedStatement ps, int i) throws SQLException {
            Shipment s = shipments.get(i);
            ps.setString(1, s.getBlNo());
            ps.setBigDecimal(2, s.getWeight());
        }
        @Override
        public int getBatchSize() {
            return shipments.size();
        }
    });
// rows[i] = i 번째 쿼리의 영향 행 수
class Shipment {
    String getBlNo() { return null; }
    java.math.BigDecimal getWeight() { return null; }
}
JdbcTemplate jdbcTemplate;
class JdbcTemplate {
    int[] batchUpdate(String s, BatchPreparedStatementSetter ss) { return null; }
}
interface BatchPreparedStatementSetter {
    void setValues(PreparedStatement ps, int i) throws SQLException;
    int getBatchSize();
}

8.3 List<Object[]> 방식

// 더 간단 (List<Object[]>)
List<Object[]> batch = new ArrayList<>();
for (Shipment s : shipments) {
    batch.add(new Object[] { s.getBlNo(), s.getWeight() });
}
int[] rows = jdbcTemplate.batchUpdate(
    "insert into shipments (bl_no, weight) values (?, ?)",
    batch);
// 간결
class Shipment {
    String getBlNo() { return null; }
    java.math.BigDecimal getWeight() { return null; }
}
JdbcTemplate jdbcTemplate;
class JdbcTemplate {
    int[] batchUpdate(String s, java.util.List<Object[]> b) { return null; }
}

8.4 성능

batchUpdate 성능:

  단건 1000회:
    - 1000 네트워크 왕복
    - 느림

  batchUpdate 1000개:
    - 1 (또는 N) 왕복
    - 빠름 (수배~수십배)

→ 대량 처리 필수

8.5 ILIC 의 맥락

// batchUpdate (ILIC)
@Repository
public class ShipmentDao {
    private final JdbcTemplate jdbcTemplate;
    public ShipmentDao(JdbcTemplate jt) { this.jdbcTemplate = jt; }
    
    // 대량 INSERT (예: CSV 가져오기)
    public int[] addBatch(List<Shipment> shipments) {
        List<Object[]> batch = shipments.stream()
            .map(s -> new Object[] { 
                s.getBlNo(), s.getWeight(), s.getStatus() })
            .collect(Collectors.toList());
        
        return jdbcTemplate.batchUpdate(
            "insert into shipments (bl_no, weight, status) values (?, ?, ?)",
            batch);
        // 한 번에 N개 → 빠름
    }
    
    // 대량 UPDATE (예: 상태 일괄 변경)
    public int[] updateStatusBatch(List<Long> ids, String status) {
        List<Object[]> batch = ids.stream()
            .map(id -> new Object[] { status, id })
            .collect(Collectors.toList());
        
        return jdbcTemplate.batchUpdate(
            "update shipments set status = ? where id = ?",
            batch);
    }
}
class Shipment {
    String getBlNo() { return null; }
    java.math.BigDecimal getWeight() { return null; }
    String getStatus() { return null; }
}
class JdbcTemplate {
    int[] batchUpdate(String s, java.util.List<Object[]> b) { return null; }
}

8.6 자기 점검 답변

batchUpdate 는?

:
1. batchUpdate:

  • 여러 SQL 묶음
  1. 사용:

    • PreparedStatementSetter / List<Object[]>
  2. 성능:

    • 왕복 ↓
  3. 활용:

    • 대량 처리

9️⃣ 면접 + 자기 점검

9.1 면접 단골 질문 매핑

Q핵심 답변
4가지 메서드?update/queryForObject/query/execute
update?INSERT/UPDATE/DELETE, 영향 행 수
queryForObject?단일 행
query?List
execute?임의 SQL (DDL)
0건?EmptyResult
2건+?IncorrectResultSize
0건 처리?Optional
단일 값?Class
batchUpdate?묶음 처리

9.2 자기 점검 체크리스트

4가지

  • 메서드

update

  • 반환 행 수

queryForObject

  • 단일

query

  • List

execute

  • 임의

0건

  • EmptyResult

N건

  • IncorrectResultSize

batchUpdate

  • 대량

9.3 추가 심화 질문

Q1: NamedParameterJdbcTemplate 의 차이?

답:

  • ? 대신 :name
  • 가독성 ↑
  • MapSqlParameterSource
  • 같은 패턴 (확장)

Q2: queryForList vs query?

답:

  • queryForList: Map (또는 단일 컬럼)
  • query: RowMapper (객체)
  • 매퍼 작성 필요 여부
  • query 가 일반적

Q3: 트랜잭션과의 통합?

답:

  • JdbcTemplate 가 TransactionSynchronizationManager 사용
  • @Transactional 의 Connection 공유
  • 자동 트랜잭션 동기화
  • Spring TM

Q4: SimpleJdbcInsert?

답:

  • INSERT 전용
  • 자동 키 받기
  • 컬럼명 자동
  • 편의 도구

Q5: 에러 변환의 SQLExceptionTranslator?

답:

  • SQLException → DataAccessException
  • DB 별 errorCode/sqlState
  • 자동 변환
  • Spring 내부

🎯 핵심 요약 — 3줄 정리

1. 4가지 핵심 메서드

  • update: INSERT/UPDATE/DELETE, 영향받은 행 수 (int) 반환
  • queryForObject: 단일 행 (객체 1개), query: 여러 행 (List)
  • execute: 임의 SQL (DDL/특수)

2. queryForObject 의 예외

  • 0건 → EmptyResultDataAccessException (Optional 권장)
  • 2건+ → IncorrectResultSizeDataAccessException (데이터 무결성 의심)
  • PK/UNIQUE 로 1건 보장된 쿼리에만 사용

3. batchUpdate

  • 여러 SQL 묶음 처리 (네트워크 왕복 1회)
  • 대량 INSERT/UPDATE 성능 (수배~수십배)

📚 다음으로...

Unit 7.4 — RowMapper

이번 Unit에서 핵심 메서드를 봤다면, 다음은 RowMapper (결과 매핑).

  • ResultSet → 객체
  • 람다 (함수형, 3주차)
  • BeanPropertyRowMapper

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 구조적 의미

6주차 누적 진행

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

총: 29/31 Unit

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

profile
Software Developer

0개의 댓글