F-LAB JAVA · 6주차 · Phase 7 · JdbcTemplate (반복 제거)
★ 깊이 파기 — 5주차 디자인 패턴의 결정체
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
JdbcTemplate 은 5주차 템플릿 메소드 + 전략 패턴의 실제 구현으로, 변하지 않는 흐름 (Connection 획득/반환·PreparedStatement 생성/해제·ResultSet 처리·예외 변환) 을 자기 안에 숨기고 개발자는 변하는 부분 (SQL·파라미터·RowMapper) 만 람다로 전달하면 되며, 이름 그대로 "Template" 이 핵심 정신이다.
JdbcTemplate 은 Spring JDBC 의 핵심 클래스로, JDBC 반복 코드를 제거하는 5주차의 템플릿 메소드 + 전략 패턴 의 실제 구현이다.
변하지 않는 부분 — Connection 획득/반환 (DataSource 자동), PreparedStatement 생성/해제, ResultSet 처리, SQLException → DataAccessException 변환 — 을 JdbcTemplate 안에 숨긴다.
개발자는 변하는 부분만 전달한다 — SQL 문자열, 파라미터, 결과 매핑 (RowMapper, 람다로 가능).
이름의 "Template" 이 그 정신을 정확히 표현한다 — 5주차의 템플릿 메소드 패턴 자체이며, JDBC 직접 코드 (try/catch/finally + 매핑 10-20줄) 가 JdbcTemplate 으로 단 3-5줄이 된다.
JdbcTemplate = 자판기:
JDBC 직접 = 매번 요리:
- 가스 켜기 (Connection)
- 팬 꺼내기 (PreparedStatement)
- 재료 (파라미터)
- 요리 (실행) ← 핵심
- 접시 (ResultSet)
- 설거지 (close 들)
- 가스 끄기 (Connection close)
JdbcTemplate = 자판기 (Template):
- 가스/팬/접시/설거지 → 자판기 안
- 사용자: 메뉴 (SQL), 토핑 (파라미터), 그릇 (RowMapper) 만
- 결과 받음
숨겨진 것:
- Connection 획득/반환
- PreparedStatement 생성/해제
- ResultSet 처리
- 예외 변환 (SQLException → DataAccessException)
개발자 = 메뉴 선택:
- SQL: "select * from shipments where id = ?"
- 파라미터: id
- 매핑: rs → Shipment
5주차 패턴:
- 자판기 = 템플릿 (변하지 않는 흐름)
- 메뉴/토핑 = 전략 (변하는 부분)
- 람다로 전달 가능
코드 차이:
- 직접: 20+ 줄
- JdbcTemplate: 3-5 줄
- 차이 4배 이상
→ JdbcTemplate = 흐름 숨김 + 변하는 부분만, 5주차 템플릿/전략의 결정체.
1. JdbcTemplate 정의
2. 5주차 패턴의 구현
3. 숨기는 것
4. 변하는 부분만
5. "Template" 이름의 의미
6. 기본 사용
7. 예외 변환
8. DataSource 주입
9. JDBC 직접 vs JdbcTemplate
JdbcTemplate:
Spring JDBC 의 핵심 클래스:
- org.springframework.jdbc.core.JdbcTemplate
- JDBC 반복 제거
- 5주차 패턴 구현
목적:
- 반복 코드 제거 (try/catch/finally)
- 자원 관리 자동
- 예외 변환
- 매핑 도구 (RowMapper)
Spring 모듈:
spring-jdbc:
- JdbcTemplate
- DataSourceTransactionManager
- DriverManagerDataSource
- 등
→ Spring JDBC 핵심
Spring Boot 자동:
spring-boot-starter-jdbc:
- JdbcTemplate 빈 자동 등록
- DataSource 자동 주입
- 별도 설정 X
→ @Autowired 만
// JdbcTemplate (ILIC)
@Repository
public class ShipmentStatisticsDao {
private final JdbcTemplate jdbcTemplate;
public ShipmentStatisticsDao(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
// Spring Boot 가 자동 주입 (HikariDataSource 기반)
}
public int countByStatus(String status) {
return jdbcTemplate.queryForObject(
"select count(*) from shipments where status = ?",
Integer.class, status);
// 한 줄로 끝
}
// ILIC: JPA 가 주력, 복잡 통계는 JdbcTemplate 보완
}
class JdbcTemplate {
<T> T queryForObject(String sql, Class<T> requiredType, Object... args) {
return null;
}
}
JdbcTemplate 의 정의는?
답:
1. JdbcTemplate:
목적:
위치:
자동:
5주차 템플릿 메소드 패턴:
부모 (템플릿):
- 변하지 않는 흐름
자식 (구체):
- 변하는 부분 (오버라이드)
→ 흐름 + 가변 부분 분리
5주차 전략 패턴:
컨텍스트:
- 변하지 않는 흐름
전략 (인터페이스):
- 변하는 부분
- 외부 주입
→ 더 유연 (자식 안 필요)
JdbcTemplate 구조:
JdbcTemplate (템플릿/컨텍스트):
- 흐름 (Connection/Statement/처리/해제)
- 모든 메서드 공통
전략 (인터페이스, 주입):
- PreparedStatementCreator
- PreparedStatementSetter
- RowMapper
- ResultSetExtractor
- 등
→ 5주차 두 패턴의 결합
// JdbcTemplate.query() 내부 (개념)
public <T> List<T> query(String sql,
PreparedStatementSetter pss,
RowMapper<T> rowMapper) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
ps = conn.prepareStatement(sql);
pss.setValues(ps); // ← 전략 (변하는 부분)
rs = ps.executeQuery();
List<T> result = new ArrayList<>();
int rowNum = 0;
while (rs.next()) {
result.add(rowMapper.mapRow(rs, rowNum++)); // ← 전략
}
return result;
} catch (SQLException e) {
throw new DataAccessException(e); // 변환
} finally {
// 자원 해제
// ...
}
}
DataSource dataSource;
interface PreparedStatementSetter { void setValues(PreparedStatement ps) throws SQLException; }
interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }
class DataAccessException extends RuntimeException { DataAccessException(Throwable t){} }
interface DataSource { Connection getConnection() throws SQLException; }
// JdbcTemplate = 5주차 패턴 (ILIC)
@Repository
public class ShipmentDao {
private final JdbcTemplate jdbcTemplate;
public ShipmentDao(JdbcTemplate jt) {
this.jdbcTemplate = jt;
}
public List<Shipment> findByStatus(String status) {
return jdbcTemplate.query(
"select * from shipments where status = ?",
// 전략 1: 파라미터 설정 (PreparedStatementSetter, 람다)
ps -> ps.setString(1, status),
// 전략 2: 매핑 (RowMapper, 람다)
(rs, rowNum) -> {
Shipment s = new Shipment();
s.setId(rs.getLong("id"));
s.setStatus(rs.getString("status"));
return s;
}
);
// JdbcTemplate (템플릿): 흐름 처리
// 람다 (전략): 변하는 부분
}
}
class Shipment {
void setId(Long id) {}
void setStatus(String s) {}
}
5주차 템플릿 메소드 + 전략 패턴의 구현인 이유는?
답:
1. 5주차 패턴:
구조:
결합:
결정체:
JdbcTemplate 이 숨기는 것:
1. Connection 획득 (DataSource 자동)
2. Connection 반환 (close)
3. PreparedStatement 생성
4. PreparedStatement 해제
5. ResultSet 처리
6. ResultSet 해제
7. SQLException → DataAccessException 변환
8. 자원 해제 순서 (역순)
9. null 체크
개발자는 모름:
- 어디서 Connection 얻나? (DataSource)
- 어떻게 PreparedStatement 만드나?
- 결과 자원 어떻게 해제?
- 예외 변환은 누가?
→ 자판기 안의 일
자동화의 가치:
- 실수 X (close 누락)
- 가독성 ↑ (본 로직만)
- 유지보수 ↑
- 일관 (모든 곳 같은 처리)
// JDBC 직접 (20+ 줄)
public Shipment getDirect(Long id) throws SQLException {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
ps = conn.prepareStatement("...");
ps.setLong(1, id);
rs = ps.executeQuery();
// 매핑
// ...
} catch (SQLException e) {
throw e;
} finally {
// close 들...
}
return null;
}
// JdbcTemplate (3-5 줄)
public Shipment get(Long id) {
return jdbcTemplate.queryForObject(
"select * from shipments where id = ?",
(rs, n) -> new Shipment(),
id);
}
class Shipment {}
JdbcTemplate jdbcTemplate;
DataSource dataSource;
class JdbcTemplate {
<T> T queryForObject(String s, RowMapper<T> r, Object... a) { return null; }
interface RowMapper<T> { T mapRow(ResultSet rs, int n); }
}
interface DataSource { Connection getConnection() throws SQLException; }
// 숨기는 것의 효과 (ILIC)
// JdbcTemplate 이 숨겨주는 것:
@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) values (?, ?)",
s.getBlNo(), s.getWeight());
// 숨겨진 것:
// - Connection 자동 획득 (HikariCP 풀에서)
// - PreparedStatement 자동 생성
// - 파라미터 바인딩
// - executeUpdate
// - 모든 자원 자동 해제
// - SQLException → DataAccessException
// - 트랜잭션 동기화 (Spring 의 TransactionManager 와)
}
public Shipment get(Long id) {
return jdbcTemplate.queryForObject(
"select * from shipments where id = ?",
(rs, n) -> {
Shipment s = new Shipment();
s.setId(rs.getLong("id"));
return s;
}, id);
// 위와 같은 처리 + ResultSet 처리
}
}
// → 개발자는 본 로직만
class Shipment {
void setId(Long id) {}
String getBlNo() { return null; }
java.math.BigDecimal getWeight() { return null; }
}
class JdbcTemplate {
void update(String s, Object... a) {}
<T> T queryForObject(String s, RowMapper<T> r, Object... a) { return null; }
interface RowMapper<T> { T mapRow(ResultSet rs, int n); }
}
JdbcTemplate 이 숨기는 것은?
답:
1. 숨기는 것:
+ 예외 변환:
자동:
효과:
개발자가 전달하는 변하는 부분:
1. SQL 문자열
2. 파라미터 (PreparedStatementSetter)
3. 결과 매핑 (RowMapper)
// SQL (변하는 부분)
jdbcTemplate.queryForObject(
"select * from shipments where id = ?", // ← SQL
...
);
// 파라미터 (가변 인자 또는 PreparedStatementSetter)
// 방법 1: 가변 인자
jdbcTemplate.queryForObject("... where id = ?", mapper, id);
// 방법 2: PreparedStatementSetter (람다)
jdbcTemplate.query("... where status = ?",
ps -> ps.setString(1, "BOOKED"), // ← Setter
mapper);
// RowMapper (매핑)
RowMapper<Shipment> mapper = (rs, rowNum) -> {
Shipment s = new Shipment();
s.setId(rs.getLong("id"));
s.setBlNo(rs.getString("bl_no"));
return s;
};
jdbcTemplate.queryForObject("...", mapper, id);
람다와 잘 맞음:
RowMapper:
- 함수형 인터페이스
- 람다 표현 가능
PreparedStatementSetter:
- 함수형 인터페이스
- 람다 표현 가능
→ 3주차 람다 활용
// 변하는 부분만 (ILIC)
@Repository
public class ShipmentDao {
private final JdbcTemplate jdbcTemplate;
public ShipmentDao(JdbcTemplate jt) { this.jdbcTemplate = jt; }
public List<Shipment> findByStatus(String status) {
return jdbcTemplate.query(
// 1. SQL (변하는 부분)
"select id, bl_no, status, weight from shipments where status = ?",
// 2. RowMapper (변하는 부분, 람다)
(rs, rowNum) -> {
Shipment s = new Shipment();
s.setId(rs.getLong("id"));
s.setBlNo(rs.getString("bl_no"));
s.setStatus(rs.getString("status"));
s.setWeight(rs.getBigDecimal("weight"));
return s;
},
// 3. 파라미터 (변하는 부분)
status
);
}
public List<Shipment> findHeavy(BigDecimal minWeight) {
return jdbcTemplate.query(
"select * from shipments where weight > ?",
(rs, n) -> new Shipment(),
minWeight
);
// 같은 메서드, 다른 SQL/파라미터/매핑
}
}
// → 변하는 부분 (3가지) 만 전달
// → 흐름은 JdbcTemplate (템플릿)
class Shipment {
void setId(Long id) {}
void setBlNo(String s) {}
void setStatus(String s) {}
void setWeight(java.math.BigDecimal w) {}
}
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); }
}
개발자는 변하는 부분만의 의미는?
답:
1. 변하는 3가지:
SQL:
파라미터:
RowMapper:
"Template" 이름:
= 템플릿 (틀, 본보기):
- 정해진 형태
- 변하지 않는 부분
- 5주차 템플릿 메소드 패턴
→ 의도 명확
5주차 정신 명시:
Spring 개발자가:
- 의도적으로 "Template" 이름
- 디자인 패턴 분명히
- "이 클래스 = 템플릿"
→ 좋은 명명
다른 Template:
Spring 의 Template 패턴:
- RestTemplate (HTTP)
- JmsTemplate (JMS)
- RedisTemplate (Redis)
- 등
→ 같은 패턴, 다른 도메인
→ 일관된 명명
명명 = 의도:
좋은 이름:
- 디자인 의도
- 사용법 암시
- 5주차 패턴 적용 신호
→ 코드 가독성
// Template 패턴들 (ILIC 에서 사용 가능)
// JdbcTemplate (DB)
@Autowired JdbcTemplate jdbcTemplate;
jdbcTemplate.query(...);
// RestTemplate (HTTP)
@Autowired RestTemplate restTemplate;
restTemplate.getForObject(...);
// RedisTemplate (Redis)
@Autowired RedisTemplate<String, Object> redisTemplate;
redisTemplate.opsForValue().set(...);
// → 모두 같은 5주차 템플릿 패턴
// → 흐름 숨기고 변하는 부분만
// → "Template" 일관
// ILIC 도:
// - JdbcTemplate 일부 사용 (복잡 쿼리)
// - 외부 API 호출 시 RestTemplate (또는 WebClient)
// - 캐시 시 RedisTemplate
class JdbcTemplate { void query(String s) {} }
class RestTemplate { <T> T getForObject(String s) { return null; } }
class RedisTemplate<K, V> { ValueOperations<K, V> opsForValue() { return null; } }
interface ValueOperations<K, V> { void set(K k, V v); }
"Template" 이름의 의미는?
답:
1. Template:
명시:
다른 Template:
일관:
// Spring Boot 자동 (별도 설정 X)
// 또는 명시
@Configuration
class JdbcConfig {
@Bean
JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
// 생성자 주입 (권장)
@Repository
public class ShipmentDao {
private final JdbcTemplate jdbcTemplate;
public ShipmentDao(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
JdbcTemplate 핵심 메서드:
update(sql, params)
- INSERT/UPDATE/DELETE
- 영향받은 행 수
queryForObject(sql, mapper, params)
- 단일 행 조회
- 객체 1개
query(sql, mapper, params)
- 여러 행 조회
- List<객체>
execute(sql)
- 임의 SQL (DDL)
// 기본 사용 (ILIC)
@Repository
public class ShipmentDao {
private final JdbcTemplate jdbcTemplate;
public ShipmentDao(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
// INSERT
public void add(Shipment s) {
jdbcTemplate.update(
"insert into shipments (bl_no, weight, status) values (?, ?, ?)",
s.getBlNo(), s.getWeight(), s.getStatus());
}
// SELECT (단일)
public Shipment get(Long id) {
return jdbcTemplate.queryForObject(
"select * from shipments where id = ?",
(rs, n) -> mapRow(rs),
id);
}
// SELECT (다수)
public List<Shipment> findByStatus(String status) {
return jdbcTemplate.query(
"select * from shipments where status = ?",
(rs, n) -> mapRow(rs),
status);
}
// UPDATE
public int updateStatus(Long id, String status) {
return jdbcTemplate.update(
"update shipments set status = ? where id = ?",
status, id);
}
// DELETE
public int delete(Long id) {
return jdbcTemplate.update(
"delete from shipments where id = ?",
id);
}
private Shipment mapRow(ResultSet rs) throws SQLException {
Shipment s = new Shipment();
s.setId(rs.getLong("id"));
s.setBlNo(rs.getString("bl_no"));
return s;
}
}
class Shipment {
void setId(Long id) {}
void setBlNo(String s) {}
String getBlNo() { return null; }
java.math.BigDecimal getWeight() { return null; }
String getStatus() { return null; }
}
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, Object... a) { return null; }
interface RowMapper<T> { T mapRow(ResultSet rs, int n) throws SQLException; }
}
JdbcTemplate 의 기본 사용은?
답:
1. 빈 등록:
주입:
핵심 메서드:
사용:
JdbcTemplate 의 예외 변환:
SQLException (Checked):
- DB 별 다른 errorCode
- DB 종속
→ 변환
DataAccessException (Unchecked):
- Spring 표준
- DB 무관
- 의미별 분류
DataAccessException 계층:
DataAccessException (Runtime)
├── DataIntegrityViolationException (제약 위반)
├── DuplicateKeyException (중복)
├── EmptyResultDataAccessException (결과 없음)
├── IncorrectResultSizeDataAccessException (결과 수 이상)
├── DeadlockLoserDataAccessException (데드락)
├── CannotAcquireLockException (락 못 얻음)
└── ...
DB 무관:
MySQL 의 "중복 키" 예외:
- SQLException + errorCode 1062
Oracle 의 "중복 키" 예외:
- SQLException + errorCode ORA-00001
→ 둘 다 DuplicateKeyException 으로
→ 같은 처리
RuntimeException (Unchecked):
- throws 불필요
- 코드 깔끔
- 전역 처리 가능 (@ExceptionHandler)
→ 모던 Spring 스타일
// 예외 변환 활용 (ILIC)
@Repository
public class ShipmentDao {
private final JdbcTemplate jdbcTemplate;
public ShipmentDao(JdbcTemplate jt) { this.jdbcTemplate = jt; }
public void add(Shipment s) {
try {
jdbcTemplate.update(
"insert into shipments (bl_no) values (?)",
s.getBlNo());
} catch (DuplicateKeyException e) {
// BL 번호 중복 (DB 무관)
throw new BusinessException("BL 번호 중복: " + s.getBlNo());
}
// SQLException 안 던짐 (DataAccessException)
// throws 불필요
}
public Shipment get(Long id) {
try {
return jdbcTemplate.queryForObject(
"select * from shipments where id = ?",
(rs, n) -> new Shipment(), id);
} catch (EmptyResultDataAccessException e) {
return null; // 결과 없음 (DB 무관)
}
}
}
// 전역 예외 핸들러
@RestControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(DataIntegrityViolationException.class)
ResponseEntity<?> handleIntegrity(DataIntegrityViolationException e) {
return null;
}
}
class Shipment { String getBlNo() { return null; } }
class JdbcTemplate {
void update(String s, Object... a) {}
<T> T queryForObject(String s, RowMapper<T> r, Object... a) { return null; }
interface RowMapper<T> { T mapRow(ResultSet rs, int n); }
}
class DuplicateKeyException extends RuntimeException {}
class EmptyResultDataAccessException extends RuntimeException {}
class DataIntegrityViolationException extends RuntimeException {}
class BusinessException extends RuntimeException { BusinessException(String s){} }
class ResponseEntity<T> {}
예외 변환 (SQLException → DataAccessException) 은?
답:
1. 변환:
계층:
DB 무관:
장점:
JdbcTemplate 의 의존:
JdbcTemplate:
- DataSource 받음 (5주차 DI)
- getConnection() 호출
- 인터페이스에만 의존
// JdbcTemplate 생성
DataSource dataSource = ...; // HikariDataSource 등
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
// 또는 setDataSource()
// Spring Boot 자동 (별도 코드 X)
@Repository
public class ShipmentDao {
@Autowired
private JdbcTemplate jdbcTemplate;
// Spring Boot 가:
// 1. HikariDataSource 빈 (auto-config)
// 2. JdbcTemplate 빈 (auto-config, DataSource 주입)
// 3. ShipmentDao 에 JdbcTemplate 주입
}
5주차 DI 의 실제:
JdbcTemplate:
- DataSource 외부 주입
- 자기가 만들지 X
- 5주차 DI 원칙
→ 좋은 사례
// DataSource 주입 (ILIC)
// Spring Boot 자동 흐름:
// 1. application.yml 에 spring.datasource.* 설정
// 2. Spring Boot 가 HikariDataSource 빈 생성
// 3. JdbcTemplate 빈 생성 (DataSource 자동 주입)
// 4. Repository 에 JdbcTemplate 주입
@Repository
public class ShipmentDao {
private final JdbcTemplate jdbcTemplate;
public ShipmentDao(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
// JdbcTemplate 가 내부적으로:
// - dataSource.getConnection() 호출 (HikariCP 풀)
// - SQL 실행
// - close() (풀로 반환)
// 모두 자동
}
// 명시적 설정 (필요 시)
@Configuration
class JdbcConfig {
@Bean
JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
// DataSource 는 또 다른 빈 (HikariDataSource)
}
}
class JdbcTemplate { JdbcTemplate(DataSource d) {} }
interface DataSource {}
DataSource 주입은?
답:
1. 의존:
생성:
Spring Boot:
5주차 DI:
// JDBC 직접 (Unit 7.1)
public Shipment getDirect(Long id) throws SQLException {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
ps = conn.prepareStatement(
"select * from shipments where id = ?");
ps.setLong(1, id);
rs = ps.executeQuery();
if (rs.next()) {
Shipment s = new Shipment();
s.setId(rs.getLong("id"));
return s;
}
return null;
} finally {
if (rs != null) try { rs.close(); } catch (SQLException e) { }
if (ps != null) try { ps.close(); } catch (SQLException e) { }
if (conn != null) try { conn.close(); } catch (SQLException e) { }
}
}
// JdbcTemplate
public Shipment get(Long id) {
return jdbcTemplate.queryForObject(
"select * from shipments where id = ?",
(rs, n) -> {
Shipment s = new Shipment();
s.setId(rs.getLong("id"));
return s;
}, id);
}
class Shipment { void setId(Long id) {} }
DataSource dataSource;
JdbcTemplate jdbcTemplate;
interface DataSource { Connection getConnection() throws SQLException; }
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; }
}
코드 줄 수:
JDBC 직접: 20+ 줄
JdbcTemplate: 5-7 줄
→ 1/4 ~ 1/3
| 항목 | JDBC 직접 | JdbcTemplate |
|---|---|---|
| 자원 관리 | 수동 (try/finally) | 자동 |
| 예외 | SQLException (Checked) | DataAccessException (Unchecked) |
| 코드 줄 | 길음 | 짧음 |
| 누수 위험 | 있음 | 없음 |
| 가독성 | ↓ | ↑ |
| 람다 | 제한적 | 적극 |
JdbcTemplate 의 마법:
사용자가 보는 것:
- 짧은 코드
실제 일어나는 것:
- 내부에서 try/catch/finally
- 자원 관리
- 예외 변환
- 5주차 패턴
→ 5주차 패턴이 작은 인터페이스로
// 전후 비교 (ILIC)
// Before (JDBC 직접, 만약)
public void addShipmentDirect(Shipment s) throws SQLException {
Connection conn = null;
PreparedStatement ps = null;
try {
conn = dataSource.getConnection();
ps = conn.prepareStatement(
"insert into shipments (bl_no, weight, status) values (?, ?, ?)");
ps.setString(1, s.getBlNo());
ps.setBigDecimal(2, s.getWeight());
ps.setString(3, s.getStatus());
ps.executeUpdate();
} finally {
if (ps != null) try { ps.close(); } catch (SQLException e) { }
if (conn != null) try { conn.close(); } catch (SQLException e) { }
}
}
// 15+ 줄
// After (JdbcTemplate)
public void addShipment(Shipment s) {
jdbcTemplate.update(
"insert into shipments (bl_no, weight, status) values (?, ?, ?)",
s.getBlNo(), s.getWeight(), s.getStatus());
}
// 4 줄
// → 1/4 줄, 가독성 ↑, 누수 X, 예외 깔끔
class Shipment {
String getBlNo() { return null; }
java.math.BigDecimal getWeight() { return null; }
String getStatus() { return null; }
}
DataSource dataSource;
JdbcTemplate jdbcTemplate;
interface DataSource { Connection getConnection() throws SQLException; }
class JdbcTemplate { void update(String s, Object... a) {} }
| Q | 핵심 답변 |
|---|---|
| JdbcTemplate? | Spring JDBC 핵심 |
| 5주차 패턴? | 템플릿 + 전략 |
| 숨기는 것? | Connection/Statement/예외 |
| 변하는 부분? | SQL/파라미터/매핑 |
| "Template" 의미? | 5주차 템플릿 |
| 예외 변환? | DataAccessException |
| DataSource? | 외부 주입 |
| 줄 수? | 1/4 |
| 람다? | RowMapper |
| Spring Boot? | 자동 |
답:
답:
답:
답:
답:
1. JdbcTemplate
2. 숨기는 것 + 변하는 것
3. 효과
이번 Unit에서 JdbcTemplate 을 봤다면, 다음은 핵심 메서드 사용법.
🛠️ 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 구조적 의미
★ 깊이 파기 — JdbcTemplate 의 등장 완료