Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ public class DataConfig {
- 해결 방법은?

#### 빈 설정파일의 변경

- 일부 빈만 바뀌어도 다른 설정 파일 필요
- 실수 가능
- 바람직하지 않음
Expand Down Expand Up @@ -210,7 +211,7 @@ spring.datasource.username=${DB_USERNAME:default}
### 프로퍼티 소스의 사용
- @Value 사용 (가장 일반적)
```java
@Component
@Co66mponent
public class MyService {
@Value("${app.name}")
private String appName;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
2.1 공통 개념
=
- 모든 기술에 공통적으로 적용되는 원칙과 기본 개념
## DAO 패턴
- DTO 또는 도메인 오브젝트만을 사용하는 인터페이스를 통해 데이터 엑세스 기술을 외부에 노출하지 않도록 만드는 것
- 코드에 영향을 주지 않고 데이터 접근 기술 변경 & 하나 이상의 기술 사용을 가능하게 해줌
- 순수한 POJO로 개발할 수 있도록 해줌
- 목 오브젝트를 사용하여 단위 테스트 가능

### DAO 인터페이스와 DI
- 인터페이스를 이용해 접근하고 DI 되도록 만들어야
- 인터페이스를 만들 때,
- 구체적 기술 노출 X
- 모든 public 메소드를 추가하는 습관 X -> Service 계층에서 의미 있는 메소드만 공개해야
- 특정 기술에만 의미있는 이름 X (ex. JPA의 persist(), merge() 등)

### 예외처리
- 데이터 접근 중 발생한 에러는 복구 불가능하기 때문에 런타임 예외여야
- 또한, DAO 메소드 선언부에
- `throws SQLException` 같은 기술을 노출하는 예외처리 x
- `throws Exception` 같은 무책임한 예외처리도 x
- DAO가 던지는 대부분의 예외는 서비스 계층 코드가 다뤄야 할 이유가 없는 예외
- but, 때로는 의미 있는 예외를 잡아서 로직에 적용하는 경우가 있음 (ex. 중복키 예외, 낙관적인 락킹 등)
- 이때, 기술마다 던지는 예외에 일관성이 없기 때문에 서비스 계층에서 이를 알고 있어야 한다는 문제 발생
- 스프링은 이를 위해 예외 추상화 제공하여 각 기술의 예외를 스프링의 데이터 예외로 변환해주는 변환 서비스를 제공
- 데이터 기술의 API를 직접 사용할 때는 AOP를 이용해 예외를 전환해주는 기능을 사용
- 최신 데이터 접근 기술을 JDBC와 다르게 런타임 예외 사용

## 2.1.2 템플릿과 API
- 데이터 접근 기술은 대부분 try/catch/finally & 반복되는 코드
- 외부 리소스와 연동 과정에서 발생하는 예외에서도 리소스 반환코드 필요하기 때문
- 스프링은 템플릿/콜백 패턴을 이용해 템플릿을 제공 -> 반복 코드 제거, 예외 변환, 트랜잭션 동기화
- 단점
- 템플릿이 제공하는 API 사용해야 함 (특정 데이터 접근 기술이 제공하는 고유한 기능이나 확장 API를 사용하려면 템플릿이 이를 지원하지 않을 수 있음)
- 콜백 오브젝트를 익명 내부 클래스로 작성해야 하는 경우 -> 가독성↓
- 따라서, 스프링은 일부 데이터 접근 기술을 API 그대로 사용할 수 있도록 제공
- 데이터 접근 기술이 제공하는 확장 기법과 API 등을 이용해 예외 변환과 트랜잭션 동기화 제공 가능하기 때문

## 2.1.3 DataSource
- Connection은 모든 데이터 접근 기술에서 사용되는 필수 리소스임
- 사용자의 요청이 빈번하게 일어나는 엔터프라이즈 환경에서 각 요청마다 커넥션을 매번 새롭게 만드는 것은 비효율적이며 성능하락
- 커넥션을 pool에 미리 준비해두고, 할당하는 풀링 기법 사용
- 스프링은 DataSource를 빈으로 등록하고 관리하기 위해 데이터 접근 기술이 자체적인 생성 방식을 사용하는 대신 스프링 빈으로 등록된 DataSource를 사용하는데 필요한 방법을 제공한다.

### 스프링에서 사용하는 DataSource의 종류
### 학습 테스트와 통합 테스트를 위한 DataSource
- 순차적으로 진행하는 통합 테스트나 단순한 학습 테스트
- 스프링이 제공하는 단순한 DataSource 사용
- but 운영환경에서는 절대 사용 x
- 설정파일을 환경별로 따로 만들어 사용 권장
#### 1. SimpleDriverDataSource
- 가장 단순한 DataSource
- 풀 관리 x, 매번 생성
#### 2. SingleConnectionDataSource
- 하나의 물리적 Connection을 계속 사용
- 순차적, 독립적 o / 멀티 스레드 x
- 매번 생성 x -> SimpleDriverDataSource 보다 빠름

### 오픈소스 또는 사용 DB 커넥션 풀
- 서버의 DB 풀로 등록해서 사용 가능하지만 일반적으로 애플리케이션 레벨에서 애플리케이션 전용 DB 풀을 만들어 사용
- 두 오픈소스 DBCP 모두 스프링 빈으로 바로 등록해서 사용 가능한 수정자 메소드를 가진 클래스가 제공되어 사용이 편리
- 다양한 풀 클래스와 설정 옵션 제공
#### 1-1. 아파치 Commons DBCP
- 가장 유명
#### 1-2. c3p0 JDBC/DataSource Resource Pool
- JDBC 3.0 스펙을 준수하는 Connection과 Statement 풀을 제공하는 라이브러리
#### 2. 상용 DB 커넥션 풀
- 일부 상용 DB는 자체적으로 커넥션 풀 라이브러리를 제공

### JDNI/WAS DB 풀
- 대부분의 자바 서버는 자체적으로 DB 풀 서비스 제공
- 서버가 제공하는 DB풀을 사용 하는 경우 JNDI를 사용
- 주의: JNDI로 가져오는 빈 오브젝트는 서버 밖에서는 제대로 동작 x -> 스프링 테스트 프레임워크는 JNDI 목 오브젝트를 제공 (`SimpleNamingContextBuilder`)



## JNDI(Java Naming and Directory Interface)
- Java에서 네트워크나 디렉토리 서비스와 같은 리소스에 접근하기 위한 표준 API
- JNDI는 복잡한 리소스 연결을 쉽게 해주기 때문에, 주로 애플리케이션 서버에서 데이터베이스, 메시지 큐, 원격 서비스 등의 리소스를 관리하고 연결할 때 사용


### JNDI는 무엇을 하는가?
- 데이터베이스 연결, 메시징 서비스, EJB(Enterprise Java Beans) 등과 같은 리소스의 이름(Name)을 통해 애플리케이션이 해당 리소스를 찾을 수 있도록 도와 줌
- 리소스를 추상화하여 사용자가 직접 리소스를 관리하거나 설정하지 않도록 도와 줌

### 왜 사용하는가?
- 리소스를 코드에 직접 하드코딩하지 않기 위해.
- 환경(개발, 테스트, 운영)에 따라 리소스 설정이 다를 수 있는데, JNDI를 사용하면 설정을 중앙화하고 관리하기 쉬워짐

### JNDI 작동 방식
#### 1. 리소스 등록
- 애플리케이션 서버(예: Tomcat, WildFly)는 데이터베이스 커넥션 풀, JMS 큐, 이메일 세션 등 다양한 리소스를 JNDI 네이밍 서비스에 등록
- 각 리소스는 고유한 이름(예: java:/comp/env/jdbc/myDataSource)으로 식별
#### 2. 리소스 검색
- 애플리케이션은 JNDI를 통해 이름으로 리소스를 조회
- JNDI는 이 요청을 처리하여 리소스 객체를 반환
258 changes: 258 additions & 0 deletions 오수진/Vol2/2장_데이터_엑세스_기술/2.2 JDBC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
2.2 JDBC
=
- 자바의 데이터 액세스 기술의 기본이 되는 로우레벨의 API
- JDBC는 인터페이스를 제공 -> 각 DB 벤더에서 구현 드라이버 제공
- 모든 데이터 접근 기술의 근간
- 장점
- 호환성
- 단순성
- 안정적이고 유연함
- 단점
- 복잡한 코드
- 일관성 없는 정보를 가진 체크 예외 처리 필요
- SQL 문자로 제공
- 릴리즈 문제
- 스프링은 장점을 가져가면서 단점을 극복하는 JDBC API 제공

## 2.2.1 스프링 JDBC 기술과 동작원리
### 스프링의 JDBC 접근 방법
- `SimpleJdbcTemplate`
- 방대한 템플릿 메소드와 내장 콜백 제공
- JDBC의 모든 기능 최대한 활용
- `SimpleJdbcInsert, SimpleJdbcCall`
- DB가 제공해주는 메타정보를 이용하여 최소한의 코드로 단순한 JDBC 코드 작성을 도와 줌
- 컬럼 정보와 파라미터 정보를 가져와서 Insert문과 프로시저 호출 작업에 사용
- `*deprecated 됐다고 함`


### 스프링 JDBC가 해주는 작업
- 다음의 작업을 스프링 JDBC가 제공하는 오브젝트에 맡길 수 있음
- 반복 작업, 실수, 에러 등
- 개발자는 데이터 액세스 로직에 따라 달라지는 부분만 정의해주면 됨
- SQL & 바인딩할 파라미터 & 쿼리 실행 결과를 받을 오브젝트 등
- DataSource를 정의 -> dataSource라는 이름의 빈으로 정의

#### 1. Connection 열기와 닫기
- 예외 발생 시 리소스 릴리즈
- 닫는 시점은 스프링 트랜잭션 기능과 맞물려서 결정

#### 2. Statement 준비와 닫기
- 개발자가 파라미터 바인딩에 사용할 정보가 담긴 맵이나 오브젝트를 전달

#### 3. Statement 실행

#### 4. ResultSet 루프
- 쿼리 결과가 한 건 이상이면 원래는 루프 -> 각 로우 처리해줘야 함
- 루프 내에서 처리될 내용은 콜백으로 제공해야 함

#### 5. 예외처리와 변환
- JDBC 작업 중 발생하는 모든 예외 처리
- 체크 예외를 런타임 예외로 전환
- 특정 기술 종속 예외를 추상적 예외로 전환 (SQLException -> DataAccessException)

#### 6. 트랜잭션 처리
- 트랜잭션 동기화 기법을 사용하여 선언적 트랜잭션 기능과 맞물려서 돌아감
- 트랜잭션 시작 후에 작업 요청 시, 진행 중인 트랜잭션에 참여 / 없으면 새로 시작
- 스프링 JDBC 사용하면 트랜잭션 작업 신경쓰지 않아도 됨 -> 스프링이 알아서 트랜잭션 선언에 따라 처리

## 2.2.2 SimpleJdbcTemplate
- 가장 많이 이용하는 JDBC 템플릿
- JdbcTemplate 을 더 편리하게 사용할 수 있도록 기능을 향상 시킨 것
- 실행, 조회, 배치의 세 가지 작업으로 구분 (실행 : INSERT, UPDATE / 조회 : SELECT / 배치 : 하나 이상의 작업을 한 번에 수행)

### SimpleJdbcTemplate 생성
```java
SimpleJdbcTemplate template = new SimpleJdbcTemplate(dataSource);
```
- SimpleJdbcTemplate는 멀티스레드 환경에서도 안전하게 공유 가능 (상태 변경 x, 공유 자원 문제 x)
- DAO의 인스턴스 변수에 저장하고 사용 가능
- 싱글톤 빈으로 등록해서 공유 가능
- 관계적으로 DAO에서 생성 방식 권장 -> 다른 방식의 JDBC 오브젝트도 만들어 사용 가능하기 때문

```java
public class MemberDao {
SimpleJdbcTemplate simpleJdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
}
}
```

### SQL 파라미터
- SimpleJdbcTemplate에 작업을 요청할 때는 SQL을 제공해야
- 위치 치환자 '?' 지원
- 이름 치환자 지원

```sql
# 원 SQL 문
INSERT INTO MEMBER(ID, NAME, POINT) VALUES (1, "Spring", 2.3);

# 위치 치환자
INSERT INTO MEMBER(ID, NAME, POINT) VALUES (?, ?, ?);

# 이름 치환자
INSERT INTO MEMBER(ID, NAME, POINT) VALUES (:id, :name, :point);
```

### Map/MapSqlParameterSource

```java
public static void main(String[] args) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("id", 1);
map.put("name", "Spring");
map.put("point", 2.3);
}
```
- Map을 바인딩 파라미터로 사용하기

```java
MapSqlParameterSource params = new MapSqlParameterSource()
.addValue("id", 1)
.addValue("name", "Spring")
.addValue("point", 3.5);
```
- 체인 형식으로 이어서 작성 가능

#### BeanPropertySqlParameterSource
- 맵 대신 도메인 오브젝트나 DTO 사용 가능
- 프로퍼티 이름과 이름 치환자 매핑

```java
public class Member {
int id;
String name;
double point;

public Member(int id, String name, double point) {
this.id = id;
this.name = name;
this.point = point;
}
}

public static void main(String[] args) {
Member member = new Member(1, "Spring", 3.5);
BeanPropertySqlParameterSource params = new BeanPropertySqlParameterSource(member);
}
```

### SQL 실행 메소드
- update() 메소드 실행
- 실행 시 바인딩 할 파라미터
#### varargs
- 위치 치환자 사용 시 팔라미터 순서대로 전달
```java
public static void main(String[] args) {
simpleJdbcTemplate.update(
"INSERT INTO MEMBER(ID, NAME, POINT, args) VALUES (?,?,?)", 1, "Spring", 3.5);

// 필요 없으면 생략 가능
simpleJdbcTemplate.delete(
"DELETE FROM MEMBER");
}
```

#### Map
- 이름 치환자 사용 시 파라미터를 Map으로 전달
```java
public static void main(String[] args) {
simpleJdbcTemplate.update(
"INSERT INTO MEMBER(ID, NAME, POINT, args) VALUES (:id, :name, :point)", map);

}
```

#### SqlParameterSource
- 도메인 오브젝트나 DTO 사용
```java
public static void main(String[] args) {
simpleJdbcTemplate.update(
"INSERT INTO MEMBER(ID, NAME, POINT, args) VALUES (:id, :name, :point)",
new BeanPropertySqlParameterSource(member));

}
```

### SQL 조회 메소드
- 값이나 오브젝트 / 리스트
#### 1. int queryForInt(String sql, [SQL 파라미터]) : 하나의 int 값 조회 시 사용
#### 2. long queryForLong(String sql, [SQL 파라미터]) : 하나의 long 값 조회 시 사용
#### 3. <T> T queryForObject(String sql, Class<T> requiredType, [SQL 파라미터]) : 하나의 값(단일 칼럼) 조회 시 사용
- 결과가 여러개면 예외 발생
- 조회되는 로우가 없다면 EmptyResultDataAccessException 발생

#### 4. <T> T queryForObject(String sql, RowMapper<T> rm, [SQL 파라미터]) : 하나의 로우(다중 칼럼) 조회 시 사용
- RowMapper에 BeanPropertyRowMapper 사용하는 것이 편리
- 생성자에 매핑할 클래스 넣어서 생성하면 RowMapper 콜백 오브젝트로 사용 가능
```java
Member m = simpleJdbcTemplate.queryForObject("select * from member where id = ?",
new BeanPropertyRowMapper<Member>(Member.class));
```

#### 5. <T> List<T> query(String sql, RowMapper<T> rm, [SQL 파라미터]) : 하나 or 여러 개의 로우(다중 칼럼) 조회 시 사용
#### 6. Map<String, Object> queryForMap(String sql, [SQL 파라미터]) : 하나의 로우 (다중 칼럼) 조회 시 사용, Map에 매핑
#### 7. List<Map<String, Object>> queryForList(String sql, [SQL 파라미터]) : 하나 or 여러 개의 로우(다중 칼럼) 조회 시 사용
- queryForMap의 다중 로우 버전

### SQL 배치 메소드
- update()로 실행하는 SQL 들을 배치 모드로 실행하게 해준다.
- 내부적으로 JDBC Statement의 addBatch(), executeBatch() 메소드 이용
- DB 호출 최소화 -> 성능 향상
- 동일한 SQL을 파라미터만 바꾸면서 실행하는 경우에 사용

#### 1. int[] batchUpdate(String sql, Map<String, ?>[] batchValues)
- 이름 치환자 & 파라미터 정보가 담긴 맵 배열 이용

#### 2. int[] batchUpdate(String sql, SqlParameterSource[] batchArgs)
- SqlParameterSource 타입 오브젝트의 배열로 파라미터 제공
- ex) MapSqlParameterSource, BeanPropertySqlParameterSource 모두 SqlParameterSource 타입

#### 3. int[] batchUpdate(String sql, List<Object[]> batchArgs)
- 위치 치환자 & Object 배열

## 2.2.3 SimpleJdbcInsert
- DB의 카탈로그 등 메타정보를 활용해 Insert 문을 간단하게

### 생성
- SimpleJdbcInsert withTableName(String tableName)
- SimpleJdbcInsert withSchemaName(String schemaName), SimpleJdbcInsert withCatalogName(String catalogName)
- SimpleJdbcInsert withColumns(String... columnsNames)
- SimpleJdbcInsert withGeneratedKeyColumns(String... columnsNames)
- 자동 생성 키 칼럼을 지정
- SimpleJdbcInsertOperations withoutTableColumnMetaDataAccess()
- DB에서 테이블 메타데이터를 가져오지 않도록 만든다

### 실행
- int execute([이름 치환자 SQL 파라미터])
- 이름 치환자를 가진 INSERT문을 내부적으로 생성
- 맵 or SqlParameterSource 타입 오브젝트로 파라미터 지정 가능
- Number executeAndReturnKey([이름 치환자 SQL 파라미터])
- 자동생성된 키 값을 Number 타입으로 돌려줌
- Number는 Integer, Long, Float, Double 등이 상속하고 있는 상위 클래스
- KeyHolder executeAndReturnKeyHolder([이름 치환자 SQL 파라미터])
- 하나 이상의 자동생성 키 컬럼을 갖는 테이블의 경우

## 2.2.4 SimpleJdbcCall
- DB에 생성해둔 저장 프로시저 또는 저장 펑션을 호출할 때 사용

### 생성
- SimpleJdbcCallOperations withProcedureName(String procedureName)
- SimpleJdbcCallOperations withFunctionName(String functionName)
- SimpleJdbcCallOperations returningResultSet(String parameterName, ParameterizedRowMapper rowMapper)
- 프로시져가 ResultSet을 돌려주는 경우에 RowMapper를 사용해 매핑

### 실행
- DB의 메타정보를 이용해 필요한 파라미터 정보를 가져온다.
- 이에 맞게 파라미터 값을 전달해야 함.
- <T> T executeFunction(Class<T> returnType, [SQL 파라미터])
- <T> T executeObject(Class<T> returnType, [SQL 파라미터])
- Map<String, Object> execute([SQL 파라미터])

## 2.2.5 스프링 JDBC DAO
- 보통 DAO는 도메인 오브젝트, DB 테이블 단위로 만들어지기 때문에 어플리케이션 하나에 여러 개의 DAO가 만들어짐
- JdbcTemplate, SimpleJdbcTemplate, SimpleJdbcInsert 등은 멀티스레드 환경에서도 안전하게 공유해서 사용 가능
- 싱글톤 빈으로 관리 가능
- but, 테이블이나 저장 프로시저 단위로 오브젝트를 만들어야 하기 때문에 DAO마다 다른 템플릿 사용
- 따라서, DataSource만 DI 받고 필요한 템플릿과 JDBC 오브젝트는 직접 생성
Loading