F-LAB JAVA · 6주차 · Phase 5 · DataSource 인터페이스 (추상화의 진화)
🏆 Phase 5 완주 — 어댑터 패턴의 실제
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
DriverManager 는 정적 메서드라 DataSource 인터페이스를 구현하지 않는데 Spring 이 DriverManager 를 DataSource 로 감싼 DriverManagerDataSource 를 만들어 같은 인터페이스로 다룰 수 있게 한 어댑터 패턴의 실제 사례이며, 풀이 없어 테스트·학습용으로만 쓰고 프로덕션엔 절대 쓰지 않는다.
문제는 DriverManager 가 DataSource 인터페이스를 구현하지 않는다 는 것 — DriverManager 는 정적 클래스 (DriverManager.getConnection(url, ...)) 라 객체가 아니기에 인터페이스 구현이 불가능하다.
Spring 의 해결책은 DriverManagerDataSource — 내부적으로는 DriverManager 의 정적 메서드를 호출하고, 외부적으로는 DataSource 인터페이스 (getConnection()) 를 제공한다.
덕분에 애플리케이션은 DataSource 에만 의존하면 되고, 테스트에서는 DriverManagerDataSource, 운영에서는 HikariDataSource 처럼 환경별로 주입만 다르게 하면 된다.
용도는 테스트 환경 (풀 불필요한 짧은 단위 테스트) 과 학습용 이며 — 풀이 없어 매 요청 새 연결 (Unit 4.1 의 비용 그대로) 이므로 프로덕션에서는 절대 쓰지 말 것 이다. 이 구조는 5주차의 어댑터 패턴 (서로 다른 인터페이스를 호환되게 변환) 의 살아있는 사례다.
DriverManagerDataSource = 콘센트-USB 어댑터:
문제:
- 가전 (애플리케이션) 은 USB-C (DataSource) 만
- DriverManager 는 옛 콘센트 (정적 메서드)
- 인터페이스 안 맞음
해결 — 어댑터:
- 한쪽: 옛 콘센트 (DriverManager 정적 호출)
- 다른쪽: USB-C (DataSource 인터페이스)
- 변환
내부 = DriverManager:
- 어댑터가 옛 콘센트 사용
외부 = DataSource:
- 가전이 보는 건 USB-C
용도:
- 학습 (어댑터 비싸지만 단순)
- 테스트 (가벼움)
프로덕션 X:
- 매번 새 연결 (풀 X)
- 비효율
- 실전엔 HikariCP
5주차 어댑터 패턴:
- "맞지 않는 인터페이스를 변환"
- DriverManagerDataSource 가 그것
→ DriverManagerDataSource = DriverManager 를 DataSource 로 감싼 어댑터, 테스트만.
1. DriverManager 가 DataSource 안 구현
2. Spring 의 해결책
3. 내부 / 외부 분리
4. 애플리케이션 일관성
5. 용도 (테스트/학습)
6. 프로덕션 X 이유
7. 어댑터 패턴 적용
8. vs HikariDataSource
9. Phase 5 완주 정리
문제:
DriverManager:
- 정적 클래스 (static 메서드)
- DriverManager.getConnection(url, ...)
DataSource:
- 인터페이스
- 인스턴스 메서드 (getConnection())
→ 인터페이스 구현 불가
정적 vs 인스턴스:
DriverManager (정적):
- 클래스 메서드
- 객체 X
- 다형성 X
DataSource (인스턴스):
- 객체 메서드
- 인터페이스 구현
- 다형성 O
호환 문제:
애플리케이션:
- DataSource 의존
DriverManager 직접 X:
- 인터페이스 안 맞음
- 사용 불가
→ 다리 필요
// 문제 (ILIC, 추상화 후)
@Repository
public class ShipmentDao {
private final DataSource dataSource; // DataSource 만
public ShipmentDao(DataSource ds) { this.dataSource = ds; }
}
// 테스트에서 DriverManager 쓰고 싶지만:
// DataSource 를 받아야 하는데 DriverManager 는 인터페이스 X
// → 어댑터 필요
class DataSourceMarker { /* placeholder */ }
interface DataSource { Connection getConnection() throws Exception; }
DriverManager 는 DataSource 안 구현의 문제는?
답:
1. 문제:
정적 vs 인스턴스:
호환:
필요:
Spring 의 해결:
DriverManagerDataSource:
- DriverManager 를 감쌈
- DataSource 인터페이스 구현
- 어댑터
→ 호환 가능
클래스 위치:
org.springframework.jdbc.datasource:
- DriverManagerDataSource
- Spring JDBC 모듈
- 표준 자바엔 없음 (Spring 제공)
// DriverManagerDataSource 사용
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setUrl("jdbc:mysql://localhost:3306/ilic");
ds.setUsername("ilic_user");
ds.setPassword("password");
// implements DataSource
// 사용
Connection c = ds.getConnection();
// 내부적으로 DriverManager.getConnection() 호출
인터페이스 호환:
DriverManagerDataSource implements DataSource:
- DataSource 받는 곳에 쓸 수 있음
- HikariDataSource 처럼
→ 일관된 사용
// Spring 의 DriverManagerDataSource (ILIC 테스트)
@Configuration
@Profile("test")
public class TestDataSourceConfig {
@Bean
public DataSource dataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("org.h2.Driver");
ds.setUrl("jdbc:h2:mem:testdb");
ds.setUsername("sa");
ds.setPassword("");
return ds;
// DriverManagerDataSource 가 DataSource 반환
}
}
// 동일한 ShipmentDao 가 이걸 받음
@Repository
public class ShipmentDao {
private final DataSource dataSource;
public ShipmentDao(DataSource ds) { this.dataSource = ds; }
// 테스트 시: DriverManagerDataSource 주입
// 운영 시: HikariDataSource 주입
// → 같은 코드
}
class DriverManagerDataSource {
void setDriverClassName(String s) {}
void setUrl(String s) {}
void setUsername(String s) {}
void setPassword(String s) {}
}
Spring 의 해결책 (DriverManagerDataSource) 은?
답:
1. DriverManagerDataSource:
위치:
사용:
호환:
내부 (구현):
DriverManagerDataSource.getConnection():
→ 내부적으로
→ DriverManager.getConnection(url, user, pwd)
→ 매번 새 연결
외부 (인터페이스):
DriverManagerDataSource:
implements DataSource
외부에서 보면:
- DataSource (인터페이스)
- getConnection() 호출
시각화:
[애플리케이션]
↓ DataSource 인터페이스
[DriverManagerDataSource] ← 어댑터
↓ 내부 호출
[DriverManager.getConnection()]
↓
[DB]
- 외부: DataSource
- 내부: DriverManager
변환:
외부 호출 (인스턴스):
ds.getConnection()
내부 변환:
DriverManager.getConnection(this.url, ...)
→ 인터페이스 변환
// 내부 / 외부 분리 (DriverManagerDataSource 동작 개념)
class DriverManagerDataSource implements DataSource {
private String url;
private String username;
private String password;
// 외부 (DataSource 인터페이스)
public Connection getConnection() throws SQLException {
// 내부 (DriverManager 호출)
return DriverManager.getConnection(url, username, password);
// → 매번 새 연결 (풀 X)
}
public void setUrl(String url) { this.url = url; }
public void setUsername(String u) { this.username = u; }
public void setPassword(String p) { this.password = p; }
}
// ILIC ShipmentDao 가 받음
@Repository
public class ShipmentDao {
private final DataSource ds; // 외부 인터페이스
// 내부가 DriverManager 든 HikariCP 든 모름
}
interface DataSource { Connection getConnection() throws SQLException; }
내부적으로 DriverManager, 외부적으로 DataSource 의 의미는?
답:
1. 내부:
외부:
시각화:
변환:
일관된 코드:
애플리케이션 (DataSource 의존):
- 운영 (HikariDataSource): OK
- 테스트 (DriverManagerDataSource): OK
- 같은 코드
// 환경별 주입 (DataSource 만 갈아끼움)
@Profile("prod")
@Bean
DataSource prodDataSource() {
// HikariDataSource (풀)
return new HikariDataSource(config);
}
@Profile("test")
@Bean
DataSource testDataSource() {
// DriverManagerDataSource (풀 없음)
return new DriverManagerDataSource(...);
}
// ShipmentDao 는 어떤 환경이든 DataSource 만 받음
추상화 이점:
- 환경별 풀 선택
- 테스트 단순 (풀 불필요)
- 같은 코드
- DIP 준수
→ 5주차 정신 실현
# 테스트 환경 (Spring Boot)
spring:
profiles: test
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
# H2 인메모리 + DriverManagerDataSource (가벼움)
// ILIC 환경별 (일관된 코드)
// 운영 (HikariCP, 풀)
@Profile("prod")
@Configuration
class ProdConfig {
@Bean
DataSource dataSource() {
HikariConfig c = new HikariConfig();
c.setJdbcUrl("jdbc:mysql://prod-mysql:3306/ilic");
c.setMaximumPoolSize(20);
return new HikariDataSource(c);
}
}
// 테스트 (DriverManagerDataSource + H2)
@Profile("test")
@Configuration
class TestConfig {
@Bean
DataSource dataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setUrl("jdbc:h2:mem:ilictest");
ds.setDriverClassName("org.h2.Driver");
return ds;
}
}
// 모든 DAO 는 같은 코드 (DataSource 만 의존)
@Repository
public class ShipmentDao {
private final DataSource ds;
public ShipmentDao(DataSource ds) { this.ds = ds; }
// 운영/테스트 동일 코드
}
class HikariConfig {
void setJdbcUrl(String s) {}
void setMaximumPoolSize(int n) {}
}
class HikariDataSource implements DataSource {
HikariDataSource(HikariConfig c) {}
public Connection getConnection() { return null; }
}
class DriverManagerDataSource implements DataSource {
void setUrl(String s) {}
void setDriverClassName(String s) {}
public Connection getConnection() { return null; }
}
애플리케이션이 DataSource 에만 의존의 효과는?
답:
1. 일관된 코드:
환경별 주입:
이점:
DIP:
DriverManagerDataSource 용도:
1. 테스트 환경
- 가벼움 (풀 X)
- 단순
- H2 인메모리
2. 학습용
- 풀 없이 DataSource 사용
- 개념 이해
3. 예외적 케이스
- 매우 짧은 작업
- 일회성
테스트 적합:
- 단위 테스트 (DB 짧게)
- 통합 테스트 (테스트 DB)
- H2 / TestContainer
풀 부담 없이 가볍게
학습용:
처음 DataSource 배울 때:
- DriverManagerDataSource
- 단순 구조
- 어댑터 패턴 이해
→ 입문
// 테스트에서 DriverManagerDataSource (ILIC)
@SpringBootTest
@ActiveProfiles("test")
class ShipmentDaoIntegrationTest {
@Autowired
private ShipmentDao shipmentDao; // DataSource 받음
@Test
void test_add() {
// 테스트 시:
// - DriverManagerDataSource (풀 X)
// - H2 인메모리 DB
// - 빠른 단위 테스트
Shipment s = new Shipment(1L);
// shipmentDao.add(s);
// 작은 단위 테스트
}
}
class ShipmentDao { void add(Shipment s) {} }
record Shipment(Long id) {}
DriverManagerDataSource 의 용도 (테스트/학습) 는?
답:
1. 용도:
테스트:
학습:
예외:
프로덕션 절대 X:
DriverManagerDataSource:
- 풀 없음
- 매 요청 새 연결
- Unit 4.1 비용 그대로
→ 운영 부적합
성능 문제:
매 요청:
- TCP handshake
- 인증
- 세션 생성
→ 수십 ms × 요청 수
→ 응답 지연 폭증
DB 부담:
- max_connections 초과 위험
- 인증 비용 누적
- 세션 폭증
→ DB 다운 가능
대안 (프로덕션):
- HikariDataSource (Spring Boot 기본)
- BasicDataSource (DBCP2)
- Tomcat JDBC Pool
→ 풀 사용 필수
ILIC 프로덕션 (HikariCP):
만약 ILIC 가 DriverManagerDataSource:
- 431 API × 1000 동시 요청
- 매 요청 새 연결
- DB 응답 시간 5ms vs 연결 50ms
- 응답 시간 폭증
- max_connections (151) 초과
- 서비스 장애
ILIC 실제 (HikariCP):
- 풀 20개
- 재사용
- 정상 운영
- 안정
→ 운영 = HikariCP 필수
→ DriverManagerDataSource = 테스트만
프로덕션에서 절대 쓰지 말 것의 이유는?
답:
1. 절대 X:
성능:
DB 부담:
대안:
어댑터 패턴 (5주차):
맞지 않는 인터페이스를:
- 호환되게 변환
- 중간에 끼움
→ 인터페이스 변환
DriverManagerDataSource = 어댑터:
Target (원하는 인터페이스):
- DataSource
Adaptee (변환 대상):
- DriverManager
Adapter:
- DriverManagerDataSource
- DataSource 구현
- 내부 DriverManager 호출
5주차 패턴 매핑:
어댑터 패턴:
- Target ← Adapter → Adaptee
여기:
- DataSource ← DriverManagerDataSource → DriverManager
자바 표준에 녹은 OOP:
좋은 라이브러리 = 좋은 OOP:
- DataSource = DIP/OCP
- DriverManagerDataSource = 어댑터
- HikariDataSource = 전략
→ 디자인 패턴의 보고
// 어댑터 패턴 (DriverManagerDataSource, ILIC)
// Target (원하는 인터페이스)
interface DataSource {
Connection getConnection() throws SQLException;
}
// Adaptee (변환 대상, 정적 클래스)
// java.sql.DriverManager (정적 메서드)
// Adapter (어댑터 — DriverManager 를 DataSource 로)
class DriverManagerDataSource implements DataSource {
private String url;
public DriverManagerDataSource(String url) { this.url = url; }
@Override
public Connection getConnection() throws SQLException {
// 내부적으로 정적 메서드 호출
return DriverManager.getConnection(url);
}
}
// 사용 (Target 인터페이스로)
@Repository
class ShipmentDao {
private final DataSource ds; // Target
public ShipmentDao(DataSource ds) {
this.ds = ds; // 어떤 구현이든 OK (어댑터 포함)
}
}
// → 5주차 어댑터 패턴의 실제 구현
// → ILIC 도 테스트에서 활용 가능
5주차 어댑터 패턴의 실제 적용은?
답:
1. 어댑터 패턴:
매핑:
Target/Adapter/Adaptee:
OOP 실제:
| 항목 | DriverManagerDataSource | HikariDataSource |
|---|---|---|
| 풀 | 없음 | 있음 (빠름) |
| 연결 | 매번 새로 | 재사용 |
| 성능 | 느림 | 빠름 |
| 용도 | 테스트/학습 | 운영 |
| Spring Boot | 명시 설정 | 자동 (기본) |
같은 인터페이스:
둘 다 implements DataSource:
- 사용 코드 동일
- getConnection() 호출
→ 추상화의 힘
성능 차이:
요청 100 회:
DriverManagerDataSource:
- 100 × 50ms (연결만)
- = 5000ms
HikariDataSource:
- 풀에서 빌림 (0.1ms × 100)
- = 10ms (500배)
선택:
운영: HikariDataSource (필수)
테스트: DriverManagerDataSource (편의)
추상화 덕분에:
- 코드 변경 X
- 빈 교체만
// vs 비교 (ILIC)
// 운영 (HikariDataSource)
@Profile("prod")
@Bean
DataSource prodDataSource() {
HikariConfig c = new HikariConfig();
c.setJdbcUrl("jdbc:mysql://prod:3306/ilic");
c.setMaximumPoolSize(20);
return new HikariDataSource(c);
// → 빠름, 풀
}
// 테스트 (DriverManagerDataSource)
@Profile("test")
@Bean
DataSource testDataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setUrl("jdbc:h2:mem:test");
return ds;
// → 가벼움, 풀 X (테스트엔 충분)
}
// ShipmentDao (어떤 환경이든)
@Repository
class ShipmentDao {
private final DataSource ds;
public ShipmentDao(DataSource ds) { this.ds = ds; }
// 코드 변경 0
// → DataSource 추상화의 힘
}
class HikariConfig {
void setJdbcUrl(String s) {}
void setMaximumPoolSize(int n) {}
}
class HikariDataSource implements DataSource {
HikariDataSource(HikariConfig c) {}
public Connection getConnection() { return null; }
}
class DriverManagerDataSource implements DataSource {
void setUrl(String s) {}
public Connection getConnection() { return null; }
}
DriverManagerDataSource vs HikariDataSource 비교는?
답:
1. 풀:
성능:
용도:
인터페이스:
Phase 5 — DataSource 인터페이스
Unit 5.1 — 다양한 커넥션 획득 방식
- DriverManager/HikariCP/DBCP2/Tomcat
- 사용법 다름 (OCP 위반)
Unit 5.2 — 추상화의 필요성
- 변경 압력 차단
- 인터페이스로
Unit 5.3 — DataSource 인터페이스 ★깊이
- javax.sql.DataSource
- getConnection() 하나
- SOLID DIP 실체
Unit 5.4 — DriverManagerDataSource
- 어댑터 패턴
- 테스트/학습
- 프로덕션 X
Phase 5 핵심 메시지:
"5주차의 추상화 정신이
자바 표준 DataSource 로 구현됐다.
애플리케이션은 DataSource 만 의존하면
풀 라이브러리를 자유롭게 교체할 수 있다."
| 5주차 | 6주차 (Phase 5) |
|---|---|
| ConnectionMaker | DataSource |
| 전략 패턴 | DataSource + 구현체 |
| DIP | DataSource 의존 |
| 어댑터 패턴 | DriverManagerDataSource |
| OCP | HikariCP ↔ DBCP2 교체 |
Phase 5 → Phase 6:
- 연결 추상화 → 트랜잭션
Phase 6 — 트랜잭션 ACID (5개 ★깊이!):
Unit 6.1 — 트랜잭션이란
Unit 6.2 — Atomicity ★깊이
Unit 6.3 — Consistency ★깊이
Unit 6.4 — Isolation ★깊이
Unit 6.5 — Durability ★깊이
Unit 6.6 — Commit 이전 동시성 ★깊이
| Q | 핵심 답변 |
|---|---|
| DriverManagerDataSource? | DriverManager → DataSource 어댑터 |
| 왜 필요? | DriverManager 인터페이스 안 함 |
| 내부/외부? | DriverManager / DataSource |
| 용도? | 테스트/학습 |
| 프로덕션 X? | 풀 없음 |
| 어댑터 패턴? | Adapter 의 실제 |
| vs Hikari? | 풀 무/유 |
| 환경별? | 주입만 다르게 |
| 자바 표준? | Spring 제공 (자바 X) |
| 일관성? | DataSource 코드 동일 |
답:
답:
답:
답:
답:
1. DriverManagerDataSource 의 이유
2. 어댑터 패턴
3. 용도와 한계
🔌 Phase 5 — DataSource 인터페이스
✅ Unit 5.1 다양한 커넥션 획득 방식
✅ Unit 5.2 추상화의 필요성
✅ Unit 5.3 DataSource 인터페이스 ★깊이
✅ Unit 5.4 DriverManagerDataSource ← 여기, Phase 5 완주
→ 다양한 풀 (OCP 위반)
→ 추상화 필요 (5주차 정신)
→ DataSource (자바 표준, DIP)
→ DriverManagerDataSource (어댑터)
💾 Phase 6 — 트랜잭션과 ACID
Unit 6.1 — 트랜잭션이란
Unit 6.2 — Atomicity (원자성) ★깊이
Unit 6.3 — Consistency (일관성) ★깊이
Unit 6.4 — Isolation (격리성) ★깊이
Unit 6.5 — Durability (지속성) ★깊이
Unit 6.6 — Commit 이전 트랜잭션 동시성 ★깊이
🧪 Part A (9 Unit) ✅
💾 Part B — DB 접근의 진화
✅ Phase 3 — JDBC (3)
✅ Phase 4 — Connection Pool (4)
✅ Phase 5 — DataSource (4) ← 완주
⏭ Phase 6 — 트랜잭션 ACID (6, ★깊이 5개!)
⏭ Phase 7 — JdbcTemplate (5, 7.2 ★깊이)
총: 20/28 Unit
🏆 Phase 5 완주 — DataSource 인터페이스