F-LAB JAVA · 6주차 · Phase 5 · DataSource 인터페이스 (추상화의 진화)
★ 깊이 파기 — 자바 표준이 된 5주차 정신
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
@Autowired DataSource) 는?
javax.sql.DataSource는 자바 표준 인터페이스로 핵심 메서드getConnection()하나만 정의하며, HikariDataSource·BasicDataSource·DriverManagerDataSource 등 모든 풀 라이브러리가 이를 구현하기에 애플리케이션은@Autowired DataSource로 인터페이스에만 의존하면 풀 교체 시 설정만 바꿔도 코드가 그대로 동작하는 SOLID DIP 의 살아있는 구현체다.
javax.sql.DataSource는 자바가 제공하는 표준 인터페이스로, 패키지가javax.sql인 것에서 알 수 있듯 자바 플랫폼의 일부다.
핵심 메서드는 단 하나 —Connection getConnection() throws SQLException— "커넥션을 얻는 방법" 만 정의한다.
구현체는 HikariDataSource (HikariCP), BasicDataSource (DBCP2), DriverManagerDataSource (Spring 제공, 풀 없음), Oracle UCP 등 자바 진영의 거의 모든 풀 라이브러리가 제공한다.
애플리케이션 코드는@Autowired private DataSource dataSource처럼 인터페이스에만 의존 하므로, HikariCP → DBCP2 변경 시 설정 파일과 의존성만 변경 하면 애플리케이션 코드는 그대로 — 5주차에서 배운 SOLID DIP (의존 역전 원칙) 의 살아있는 구현체이며, Spring Boot 는 기본으로 HikariDataSource 를 빈으로 자동 등록한다.
DataSource = 자바 표준 USB:
자바 표준 인터페이스 (USB-C 같은):
- javax.sql.DataSource
- 단 하나 핵심 메서드: getConnection()
- 자바 플랫폼의 일부 (모든 자바에 있음)
구현체 (USB-C 케이블들):
- HikariDataSource (HikariCP)
- BasicDataSource (DBCP2)
- DriverManagerDataSource (Spring, 풀 없음)
- 그 외 Oracle UCP, Tomcat 등
모두 같은 인터페이스 구현
→ 호환
애플리케이션 (USB-C 포트 가전):
- DataSource (USB-C) 만 알면 됨
- @Autowired DataSource
- 어느 케이블이든 꽂힘
교체 (케이블 교체):
- HikariCP → DBCP2
- 설정·의존성 변경
- 가전(코드) 그대로
SOLID DIP:
- 가전 → USB-C 인터페이스 ← 케이블
- 둘 다 표준 (추상) 의존
→ DataSource = 자바 표준 인터페이스 (getConnection 하나), 모든 풀 구현, DIP 의 실체.
1. javax.sql.DataSource 정의
2. 핵심 메서드 getConnection()
3. 구현체들
4. 애플리케이션 코드 (@Autowired)
5. HikariCP → DBCP2 변경 범위
6. SOLID DIP 의 구현체
7. Spring Boot 기본
8. 추가 메서드와 부가 기능
9. 면접 + 자기 점검
javax.sql.DataSource:
패키지:
- javax.sql
- 자바 표준 (JDK)
- 자바 1.4+ 부터
→ 자바 플랫폼의 일부
// javax.sql.DataSource (자바 표준)
package javax.sql;
public interface DataSource extends CommonDataSource {
Connection getConnection() throws SQLException;
Connection getConnection(String username, String password)
throws SQLException;
// ... 추가 메서드 (Wrapper 관련)
}
표준의 위치:
javax.sql:
- javax = 자바 표준 확장
- sql = SQL 관련
- 자바 전체와 함께
→ 자바를 쓰면 무조건 있음
JDBC 와 관계:
java.sql.* :
- Connection
- PreparedStatement
- ResultSet
javax.sql.* :
- DataSource (확장)
- 풀/분산 트랜잭션 등
→ JDBC 확장 표준
// javax.sql.DataSource (ILIC)
import javax.sql.DataSource; // 자바 표준 import
@Repository
public class ShipmentDao {
private final DataSource dataSource; // 자바 표준 인터페이스
public ShipmentDao(DataSource ds) {
this.dataSource = ds;
}
// ILIC 가 어떤 풀 쓰든 javax.sql.DataSource 면 OK
// - Spring Boot 의 HikariDataSource
// - 테스트 시 DriverManagerDataSource
// - 다른 풀 라이브러리
}
javax.sql.DataSource 의 정의는?
답:
1. 패키지:
자바 표준:
선언:
JDBC 확장:
// 핵심 메서드
Connection getConnection() throws SQLException;
의미:
"커넥션을 얻는 방법" :
- 단 하나
- "어떻게 얻든" 자유 (구현)
- 표준 행위
→ 가장 본질만 추상화
단순함의 힘:
메서드 하나:
- 모든 구현체 합의 쉬움
- 인터페이스 안정
- 호환성 ↑
→ 작은 표면적
// 호출
DataSource ds = ...; // 어떤 구현체든
Connection c = ds.getConnection();
try {
// 사용
} finally {
c.close();
}
// 모든 DataSource 구현체에서 동일
구현의 자유:
getConnection() 안에서:
- 풀에서 빌림 (HikariCP)
- 직접 연결 (DriverManager)
- Mock 반환 (테스트)
- 마음대로
→ 외부는 모름
// 핵심 메서드 (ILIC)
@Repository
public class ShipmentDao {
private final DataSource dataSource;
public ShipmentDao(DataSource ds) {
this.dataSource = ds;
}
public Shipment get(Long id) throws SQLException {
// 핵심: getConnection() 호출
try (Connection c = dataSource.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(
"select * from shipments where id = ?")) {
ps.setLong(1, id);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return new Shipment();
}
}
}
}
return null;
}
// dataSource.getConnection() 만 호출
// 풀이든 직접이든 똑같이
}
class Shipment {}
핵심 메서드 getConnection() 의 의미는?
답:
1. 하나의 메서드:
의미:
단순:
구현 자유:
DataSource 구현체:
1. HikariDataSource (HikariCP)
2. BasicDataSource (DBCP2)
3. DriverManagerDataSource (Spring 제공)
4. DataSource (Tomcat JDBC Pool)
5. PoolDataSource (Oracle UCP)
6. ... 등 풀 라이브러리들
// HikariDataSource (HikariCP, Spring Boot 기본)
HikariConfig config = new HikariConfig();
config.setJdbcUrl("...");
HikariDataSource ds = new HikariDataSource(config);
// implements DataSource
// 풀 기반, 가장 빠름
// BasicDataSource (DBCP2)
BasicDataSource ds = new BasicDataSource();
ds.setUrl("...");
ds.setMaxTotal(20);
// implements DataSource
// Apache, 전통 풀
// DriverManagerDataSource (Spring)
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setUrl("...");
ds.setUsername("...");
ds.setPassword("...");
// implements DataSource
// 풀 없음 (DriverManager 어댑터)
// 테스트/학습용
// 어떤 구현체든 (같은 사용법)
DataSource ds = ...; // Hikari? DBCP? 모름
Connection c = ds.getConnection();
// 모두 같은 인터페이스로
// 다양한 구현체, 같은 코드 (ILIC)
public class ShipmentDao {
private final DataSource dataSource;
public ShipmentDao(DataSource ds) { this.dataSource = ds; }
// dataSource 가 어떤 구현체든 OK
}
// 환경별 주입
@Configuration
@Profile("prod")
class ProdConfig {
@Bean DataSource dataSource() {
// HikariDataSource (운영, 풀)
return null;
}
}
@Configuration
@Profile("test")
class TestConfig {
@Bean DataSource dataSource() {
// DriverManagerDataSource + H2 (테스트, 풀 없음)
return null;
}
}
// ShipmentDao 는 그대로
// 구현체만 환경별
DataSource 구현체들은?
답:
1. 주요:
HikariCP:
DBCP2:
DriverManagerDataSource:
// Spring DI (필드/생성자)
@Repository
public class ShipmentDao {
@Autowired
private DataSource dataSource; // 인터페이스만
// 또는 생성자 (권장)
// public ShipmentDao(DataSource ds) { ... }
}
인터페이스 의존:
@Autowired private DataSource dataSource:
- DataSource (인터페이스) 만
- 구체 (HikariDataSource) 모름
- Spring 이 알아서 주입
→ 완벽한 추상화
// 간결한 DAO 코드
@Repository
public class ShipmentDao {
private final DataSource dataSource;
public ShipmentDao(DataSource ds) {
this.dataSource = ds;
}
public Shipment get(Long id) throws SQLException {
try (Connection c = dataSource.getConnection()) {
// SQL
}
return null;
}
}
// 풀 설정 X (외부 자동)
// 인터페이스만 의존
// 여러 빈이 같은 DataSource
@Repository
public class BookingDao {
private final DataSource dataSource; // 같은 빈
public BookingDao(DataSource ds) { this.dataSource = ds; }
}
@Repository
public class InvoiceDao {
private final DataSource dataSource; // 같은 빈
public InvoiceDao(DataSource ds) { this.dataSource = ds; }
}
// Spring 이 같은 HikariDataSource 빈 주입 (싱글톤)
// → 풀 공유
class Shipment {}
// @Autowired DataSource (ILIC)
// 모든 DAO 가 인터페이스에만 의존
@Repository
public class ShipmentDao {
private final DataSource dataSource; // 인터페이스
public ShipmentDao(DataSource ds) { this.dataSource = ds; }
}
@Repository
public class BookingDao {
private final DataSource dataSource; // 같은 인터페이스
public BookingDao(DataSource ds) { this.dataSource = ds; }
}
// ILIC 의 102 테이블 DAO 모두:
// - DataSource 인터페이스만 의존
// - Spring 이 HikariDataSource (싱글톤) 주입
// - 풀 변경 시 DAO 코드 X
// → 5주차 DI 가 6주차 DataSource 와 만남
애플리케이션 코드 (@Autowired DataSource) 는?
답:
1. 주입:
인터페이스 의존:
간결:
공유:
변경 범위:
HikariCP → DBCP2:
1. 설정 파일 (application.yml)
2. 의존성 (build.gradle)
애플리케이션 코드:
- 변경 X (인터페이스 의존)
→ 최소 변경
# Before (HikariCP)
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
hikari:
maximum-pool-size: 20
# After (DBCP2)
spring:
datasource:
type: org.apache.commons.dbcp2.BasicDataSource
dbcp2:
max-total: 20
# 설정 키만 변경 (코드 X)
// Before
implementation 'com.zaxxer:HikariCP'
// After
implementation 'org.apache.commons:commons-dbcp2'
// 코드 변경 X
@Repository
public class ShipmentDao {
private final DataSource dataSource; // 인터페이스
public ShipmentDao(DataSource ds) { this.dataSource = ds; }
// ↑ HikariCP/DBCP2 무관
}
OCP 실현:
새 풀 도입 = 확장 ✅
애플리케이션 코드 그대로 = 닫힘 ✅
→ OCP 달성
HikariCP → DBCP2 (ILIC, 가상 시나리오)
ILIC 가 풀 교체할 때:
1. 설정 (application.yml):
- hikari 설정 → dbcp2 설정
2. 의존성 (build.gradle):
- HikariCP exclude
- commons-dbcp2 add
3. 애플리케이션 코드:
- 102 테이블 DAO 그대로
- 431 API 그대로
- Service/Controller 그대로
→ 코드 변경 0
→ 설정/의존성만
(Unit 5.1 의 악몽 vs 인터페이스 도입 후)
(102 개 수정 → 설정 한 줄)
HikariCP → DBCP2 변경 시 변경 범위는?
답:
1. 변경 범위:
설정:
의존성:
코드:
DIP (의존 역전 원칙):
"고수준 모듈은 저수준 모듈에 의존 X
둘 다 추상에 의존"
- 추상이 중심
- 구체는 추상을 가리킴
DataSource = DIP 실체:
고수준 (애플리케이션):
ShipmentDao → DataSource (추상)
저수준 (풀):
HikariDataSource → DataSource (추상)
둘 다 DataSource 가리킴
→ DIP
의존 방향:
ShipmentDao ────→ DataSource ←──── HikariDataSource
(고수준) (추상) (저수준)
- 고수준 → 추상 (의존)
- 저수준 → 추상 (구현)
- 둘 다 추상 의존
5주차 ↔ 자바 표준:
5주차 (자체):
- ConnectionMaker
자바 표준:
- DataSource
같은 DIP 정신
→ 자바가 표준화
OOP 의 실제:
좋은 라이브러리:
- SOLID 원칙
- 자바 표준에 녹음
배움:
- 라이브러리 = OOP 교본
// DataSource = DIP 실체 (ILIC)
// 추상 (DataSource — 자바 표준)
interface DataSource {
Connection getConnection() throws Exception;
}
// 고수준 (애플리케이션) — 추상 의존
@Repository
public class ShipmentDao {
private final DataSource ds; // DataSource (추상)
public ShipmentDao(DataSource ds) { this.ds = ds; }
}
// 저수준 (풀) — 추상 구현
class HikariDataSource implements DataSource {
public Connection getConnection() { return null; }
}
// 의존 그래프:
// ShipmentDao ─→ DataSource ←─ HikariDataSource
// (ILIC 코드) (자바 표준) (외부 라이브러리)
// → 5주차 DIP 의 자바 표준 구현
// → ILIC 가 좋은 OOP 구조
DataSource = SOLID DIP 의 구현체인 이유는?
답:
1. DIP:
구현체:
5주차 ↔ 자바:
OOP 실제:
Spring Boot 자동:
spring-boot-starter-jdbc 또는
spring-boot-starter-data-jpa:
- HikariDataSource 자동 빈
- DataSource 인터페이스 노출
→ 설정만 (application.yml)
// Spring Boot 가 자동
// DataSourceAutoConfiguration 가:
// - HikariDataSource 빈 등록
// - application.yml 의 spring.datasource.* 적용
// - DataSource 타입으로 노출
// 사용자는 그저:
@Autowired
private DataSource dataSource; // 자동 주입
// HikariDataSource 가 실제로 주입됨
starter 별:
spring-boot-starter-jdbc:
- JdbcTemplate + HikariCP
spring-boot-starter-data-jpa:
- JPA + Hibernate + HikariCP
모두 HikariCP 기본
# 다른 풀로 변경
spring:
datasource:
type: org.apache.commons.dbcp2.BasicDataSource
# Spring Boot 가 BasicDataSource 자동 등록 (의존성 있으면)
# ILIC application.yml
spring:
datasource:
url: jdbc:mysql://mysql:3306/ilic
username: ilic_user
password: ${DB_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
max-lifetime: 1800000
# Spring Boot 가:
# 1. HikariDataSource 빈 자동 등록
# 2. 위 설정 적용
# 3. DataSource 타입 노출
# 4. 모든 @Autowired DataSource 에 주입
# → ILIC 의 모든 DAO 가 같은 HikariDataSource 공유
# → 풀 1개, 코드 0줄 (설정만)
Spring Boot 기본 DataSource 는?
답:
1. 자동:
등록:
starter:
설정:
// DataSource 의 추가 메서드
public interface DataSource extends CommonDataSource {
Connection getConnection() throws SQLException;
// 인증 정보 직접
Connection getConnection(String username, String password)
throws SQLException;
// Wrapper (CommonDataSource)
// ...
}
// 사용자 지정 인증
Connection c = ds.getConnection("special_user", "special_pwd");
// 풀의 기본 인증 X, 별도 인증
// (드묾, 멀티 테넌트 등)
CommonDataSource:
DataSource 의 부모:
- getLogWriter
- setLogWriter
- getLoginTimeout
- setLoginTimeout
- getParentLogger
→ 공통 부가 기능
XADataSource:
분산 트랜잭션:
- 여러 DB 한 트랜잭션
- 2PC (Two-Phase Commit)
- 엔터프라이즈
→ DataSource 의 확장
ConnectionPoolDataSource:
풀 전용 인터페이스:
- getPooledConnection
- 풀 구현체 표준
→ 풀 측 인터페이스
// 추가 메서드 (ILIC, 거의 안 씀)
@Repository
public class ShipmentDao {
private final DataSource dataSource;
public ShipmentDao(DataSource ds) { this.dataSource = ds; }
public Connection getStandard() throws SQLException {
return dataSource.getConnection(); // 보통 사용
}
public Connection getCustom() throws SQLException {
// 멀티 테넌트 등 특수 케이스 (드묾)
return dataSource.getConnection("tenant_user", "tenant_pwd");
}
// ILIC 는 표준 getConnection() 만 사용
// 인증 별도는 거의 X
}
DataSource 추가 메서드는?
답:
1. 추가:
CommonDataSource:
XADataSource:
실무:
| Q | 핵심 답변 |
|---|---|
| DataSource? | 자바 표준 인터페이스 |
| 패키지? | javax.sql |
| 핵심 메서드? | getConnection() |
| 구현체? | Hikari/DBCP2/DriverManagerDataSource |
| @Autowired? | 인터페이스 의존 |
| HikariCP → DBCP2? | 설정만 |
| DIP? | 둘 다 추상 의존 |
| Spring Boot? | HikariDataSource 자동 |
| 5주차 ↔ 자바? | ConnectionMaker = DataSource |
| 추가 메서드? | getConnection(user, pwd) |
답:
답:
답:
답:
답:
1. javax.sql.DataSource
getConnection() 하나2. 애플리케이션의 추상화
@Autowired DataSource → 인터페이스만 의존3. SOLID DIP 의 실체
이번 Unit에서 DataSource 인터페이스를 봤다면, 다음은 DriverManagerDataSource (Phase 5 마지막).
🔌 Phase 5 — DataSource 인터페이스
✅ Unit 5.1 다양한 커넥션 획득 방식
✅ Unit 5.2 추상화의 필요성
✅ Unit 5.3 DataSource 인터페이스 ★깊이 ← 여기
⏭ Unit 5.4 DriverManagerDataSource — Phase 5 완주
🧪 Part A (9 Unit) ✅
💾 Part B — DB 접근의 진화
✅ Phase 3 — JDBC (3)
✅ Phase 4 — Connection Pool (4)
🔌 Phase 5 — DataSource (3/4)
총: 19/28 Unit
★ 깊이 파기 — DataSource 인터페이스 완료