F-LAB JAVA · 6주차 · Phase 7 · JdbcTemplate (반복 제거)
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
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 에 사용한다.
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, 각자 분명한 용도.
1. 핵심 메서드 4가지
2. update()
3. queryForObject()
4. query()
5. execute()
6. queryForObject 결과 0건
7. queryForObject 결과 N건
8. batchUpdate
9. 면접 + 자기 점검
JdbcTemplate 4가지 핵심:
update(sql, params)
- INSERT/UPDATE/DELETE
- 영향받은 행 수 (int)
queryForObject(sql, mapper, params)
- 단일 행 조회
- 객체 1개
query(sql, mapper, params)
- 여러 행 조회
- List<T>
execute(sql)
- 임의 SQL (DDL)
분명한 분리:
쓰기 (변경) → update
단일 읽기 → queryForObject
목록 읽기 → query
기타 → execute
→ 메서드만 봐도 의도 명확
추가 메서드:
- batchUpdate (대량)
- queryForList (List<Map>)
- queryForMap (단일 Map)
- queryForRowSet (RowSet)
→ 편의 메서드 다수
// 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; }
}
핵심 메서드 4가지는?
답:
1. update:
queryForObject:
query:
execute:
// 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 = 삭제된 행 수
update() 반환:
영향받은 행 수 (int):
- 0: 조건에 맞는 행 없음
- N: N개 변경
→ executeUpdate() 와 같음
// 활용 (영향받은 행 수)
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; } }
// 자동 키 생성 (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; }
// 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; } }
update(sql, params) 의 동작은?
답:
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; }
}
// 단일 값 (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; }
}
결과 1건 가정:
- 0건: 예외
- 1건: 객체 반환
- 2건 이상: 예외
→ 1건 보장된 곳에서만
1건 보장 예:
- PK 로 조회: WHERE id = ?
- UNIQUE 컬럼: WHERE bl_no = ?
- 집계: COUNT(*), SUM(*)
→ 의미상 1건
// 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; }
}
queryForObject(sql, mapper, params) 의 동작은?
답:
1. 사용:
단일 값:
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; }
}
0건이면:
- 예외 X
- 빈 리스트 (List.of() 또는 ArrayList())
- 안전
→ queryForObject 와 다른 점
// 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; }
// 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;
// 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; }
}
query(sql, mapper, params) 의 동작은?
답:
1. 사용:
반환:
0건:
편의:
execute():
임의 SQL 실행:
- 결과 처리 X
- DDL (CREATE/DROP)
- 특수 명령
// 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) {} }
// 특수 (truncate, set 등)
jdbcTemplate.execute("truncate table audit_logs");
jdbcTemplate.execute("set global max_connections = 200");
보통 안 씀:
운영 코드에서:
- 거의 update/query 만
- DDL 은 마이그레이션 도구
- Flyway, Liquibase
→ execute 는 특수 케이스
// 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) {} }
execute(sql) 의 용도는?
답:
1. execute:
DDL:
특수:
운영:
queryForObject 결과 0건:
→ EmptyResultDataAccessException
- RuntimeException
- "결과 0건인데 1건 기대"
// 처리 패턴
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 {}
// 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 {}
의도된 예외:
PK 로 조회:
- 보통 1건 있음
- 없으면 의외 (예외)
존재 확인:
- count(*) 가 더 적합
- queryForObject 부적합
// 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 {}
queryForObject 결과 0건이면?
답:
1. 0건:
처리:
권장:
대안:
queryForObject 결과 2건 이상:
→ IncorrectResultSizeDataAccessException
- 1건 기대, 실제 N건
- 데이터 무결성 의심
- 또는 쿼리 잘못
원인:
- WHERE 조건 부족 (UNIQUE 가정인데 중복)
- 데이터 중복 (제약 누락)
- 쿼리 실수
→ 코드 또는 데이터 문제
// 보통 잡지 않음 (의외)
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;
예방:
- PK / UNIQUE 컬럼으로 조회
- DB 제약 (UNIQUE) 사용
- 쿼리 검토
→ 1건 보장 쿼리만
// 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); }
queryForObject 결과 2건 이상이면?
답:
1. N건:
원인:
처리:
예방:
batchUpdate:
여러 SQL 한꺼번에:
- 네트워크 왕복 1회
- 성능 ↑
- 대량 처리
→ INSERT/UPDATE 다수
// 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();
}
// 더 간단 (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; }
}
batchUpdate 성능:
단건 1000회:
- 1000 네트워크 왕복
- 느림
batchUpdate 1000개:
- 1 (또는 N) 왕복
- 빠름 (수배~수십배)
→ 대량 처리 필수
// 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; }
}
batchUpdate 는?
답:
1. batchUpdate:
사용:
성능:
활용:
| Q | 핵심 답변 |
|---|---|
| 4가지 메서드? | update/queryForObject/query/execute |
| update? | INSERT/UPDATE/DELETE, 영향 행 수 |
| queryForObject? | 단일 행 |
| query? | List |
| execute? | 임의 SQL (DDL) |
| 0건? | EmptyResult |
| 2건+? | IncorrectResultSize |
| 0건 처리? | Optional |
| 단일 값? | Class |
| batchUpdate? | 묶음 처리 |
답:
답:
답:
답:
답:
1. 4가지 핵심 메서드
update: INSERT/UPDATE/DELETE, 영향받은 행 수 (int) 반환queryForObject: 단일 행 (객체 1개), query: 여러 행 (List)execute: 임의 SQL (DDL/특수)2. queryForObject 의 예외
EmptyResultDataAccessException (Optional 권장)IncorrectResultSizeDataAccessException (데이터 무결성 의심)3. batchUpdate
이번 Unit에서 핵심 메서드를 봤다면, 다음은 RowMapper (결과 매핑).
🛠️ 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 구조적 의미
🧪 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 · 끝