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
File renamed without changes.
164 changes: 164 additions & 0 deletions 이가연/vol 2./2장 데이터 액세스 기술.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# 2장 데이터 액세스 기술

토비에서는 데이터 액세스 기술로 JDBC와 MyBatis, SqlMaps를 다루고 있지만, 최근 트렌드를 고려해 JPA에 대해서 깊게 서술하는 것으로 대신하고자 한다 !

# DAO 패턴

데이터 액세스 계층을 DAO 계층이라 부른다.

**데이터 액세스 기술은 외부에 노출되지 않는 것이 중요하다.**

그 이유는, 데이터 액세스 기술이 외부에 노출되면, **비즈니스 로직이 특정 기술에 종속**될 수 있기 때문이다. 이는 확장성을 저하하는 행위이다.

따라서 DAO는 **인터페이스로 정의하고, 구체적인 구현체는 내부에서 캡슐해 DI로 되도록** 하는 것이 일반적이다.

이를 통해 구체적인 구현체(의존체)가 변경된다 하더라도, 비즈니스 로직은 사용하던 메서드를 그대로 사용할 수 있다.

# JPA

최근 가장 많이 사용되는 데이터 액세스 기술은 JPA(Java Persistence API)이다.

대표적인 ORM 프레임워크로, SQL Mapper와 함께 Persistence Framework로 분류된다.

참고로, JDBC는 ORM도 SQL Mapper도 아닌, 가장 저수준의 데이터베이스 액세스 기술이다. 모든 **Persistance Framework는 내부적으로 JDBC API를 근간으로써 이용**한다.

## ORM VS SQL Mapper

ORM은 DB 객체를 자바 객체로 매핑함으로써 객체 간의 관계를 바탕으로 SQL을 자동으로 생성해주지만, SQL Mapper는 개발자가 직접 SQL을 명시하고 관리해야 한다.

ORM은 **데이터베이스의 관계를 객체에 반영**하는 것이 목적이라면, SQL Mapper는 단순히 **필드를 매핑**시키는 것이 목적이라는 점에서 지향점이 다르다.

## JPA는 기술 명세이다.

주의할 점은, JPA는 특정 기능을 하는 라이브러리가 아니라, 인터페이스라는 점이다. 객체지향프로그래밍에서 관계형 데이터베이스를 어떻게 사용해야 하는지 방법을 정의한 명세서인 셈이다.

JPA를 정의한 javax.persistence 패키지는 각종 interface, enum, exception, annotation 등으로 구성되어있는데, JPA의 핵심이 되는 EntityManager 또한 interface로 정의돼있으며, 구현체가 존재하지 않는다.

```java
package javax.persistence;

import ...

public interface EntityManager {

public void persist(Object entity);

public <T> T merge(T entity);

public void remove(Object entity);

public <T> T find(Class<T> entityClass, Object primaryKey);

// More interface methods...
}
```

명세서이기 때문에 구현은 없다. 이것을 구현한 게 Hibernate이다.

## Hibernate는 JPA의 구현체이다.

Hibernate는 **JPA라는 명세의 구현체**이다.

![image](https://github.com/user-attachments/assets/0bc0aa37-62a2-4901-a947-42796bc9d141)

위 사진은 JPA와 Hibernate의 상속 및 구현 관계를 나타낸 것이다. JPA의 핵심인 EntityManagerFactory, EntityManager, EntityTransaction을 Hibernate에서는 각각 SessionFactory, Session, Transaction으로 상속받고 각각 Impl로 구현하고 있음을 확인할 수 있다. 내부적으로도 여전히 JDBC API를 사용한다.

## JPA를 씀으로써 이점

객체지향 언어인 Java와 관계지향 데이터베이스인 RDBMS는 **근본적으로 다른** 모델이다. 이러한 **패러다임 불일치를 JPA가 완충**해준다. 개발자가 **객체를 다루듯이 데이터를 조작**할 수 있기 때문이다.

또한, 기존의 SQL Mapper는 SQL문을 개발자가 직접 관리해야 했던 반면, JPA를 씀으로써 SQL 관리 작업에서 어느 정도 탈출할 수 있게 됐다. 물론 복잡한 쿼리문은 여전히 JPQL로 관리해야 하지만 말이다.

# 영속성 컨텍스트

Persistence Context는 어플리케이션과 데이터베이스 사이에서 엔티티 객체를 관리하는 메모리 공간이다. Entity Manager가 영속성 컨텍스트를 제어한다.

## 엔티티 생명 주기

![image](https://github.com/user-attachments/assets/60e94908-8b33-4238-8165-559f30a2a650)

영속성 컨텍스트는 엔티티 객체의 상태를 관리하며, 엔티티는 트랜잭션 내에서 다음과 같은 생명 주기를 가진다.

- `비영속`(**Transient)**
- 영속성 컨텍스트와 연관되지 않은 상태
- `영속`(**Persistent**)
- 영속성 컨텍스트에 저장되어 관리되는 상태
- **영속 상태의 엔티티는 데이터베이스와 동기화**되며, **영속성 컨텍스트가 변경 사항을 추적**힌다. (더티체킹도 이러한 메커니즘으로 동작하는 것.)
- `persist` 메서드로 객체를 영속 상태로 전환한다.

```java
entityManager.persist(user); // 영속 상태로 전환
```

- `준영속`(**Detached**)
- 영속성 컨텍스트에서 분리된 상태이다.
- 더이상 영속성 컨텍스트에서 관리되지 않으며, 변경 사항 또한 데이터베이스에 반영되지 않는다.
- `detach` 메서드로 영속 엔티티를 영속성 컨텍스트로부터 분리시킨다.
- 반대로 `merge` 메서드로 준영속 엔티티를 영속 상태로 병합하기도 한다.

```java
entityManager.detach(user); // 영속 -> 준영속 상태로 전환
entityManager.merge(user); // 준영속 -> 영속 상태로 전환
```

- `삭제`(**Removed**)
- 영속성 컨텍스트에서 관리되지만 삭제 예약 상태이다.
- `remove` 메서드로 수행된다.
- 삭제 트랜잭션이 커밋되면 데이터베이스에서 삭제된다.

```java
entityManager.remove(user); // 삭제 예약
```


## 영속성 컨텍스트 메서드

| **메서드** | **설명** | **주요 특징** |
| --- | --- | --- |
| `persist()` | 엔티티를 영속 상태로 만듦 | 새로 생성된 엔티티를 영속성 컨텍스트에 추가. 데이터베이스에 즉시 저장되지 않음. |
| `find()` | 식별자로 엔티티를 조회 | 1차 캐시 확인 후 없으면 데이터베이스에서 조회. |
| `merge()` | 준영속 상태의 엔티티를 영속 상태로 병합 | 반환된 객체가 영속 상태, 기존 객체는 준영속 상태로 유지. |
| `remove()` | 엔티티를 삭제 상태로 전환 | 영속 상태의 엔티티만 삭제 가능. 트랜잭션 커밋 시 삭제 실행. |
| `detach()` | 엔티티를 준영속 상태로 만듦 | 엔티티와 영속성 컨텍스트의 연결 끊음. 변경 감지 동작하지 않음. |
| `clear()` | 영속성 컨텍스트 초기화 | 모든 엔티티를 준영속 상태로 전환. |
| `flush()` | 쓰기 지연 SQL 저장소를 데이터베이스와 동기화 | 데이터베이스에 반영되지만 트랜잭션은 유지. |
| `refresh()` | 엔티티를 데이터베이스의 최신 상태로 갱신 | 1차 캐시의 상태를 무효화하고 데이터베이스에서 다시 조회. |

## **1차 캐시란?**

![image](https://github.com/user-attachments/assets/e7c619a7-bd70-407e-90b6-8c1ca1aa156d)

**영속성 컨텍스트 내부에 존재하는 캐시**로, 엔티티를 메모리에 저장한다.
데이터베이스 접근 전, 1차 캐시에 엔티티가 조회한다면 이를 캐싱해 데이터베이스 접근을 줄이고 성능을 최적화하는 역할을 한다. 1차 캐시는 식별자로 엔티티를 구분하며, **동일한 식별자를 가진 엔티티를 재사용**한다.

**1차 캐시 주요 동작**

1. 엔티티 조회
1. 조회 동작시, JPA는 먼저 1차 캐시를 확인한다.
2. 캐시가 히트하면 캐시된 엔티티를 반환하고, 미스하면 DB를 조회한다.
2. 엔티티 저장
1. persist() 메서드를 호출해 엔티티를 저장하면 1차 캐시에 추가된다.
2. DB에는 즉시 반영되지 않고, 트랜잭션 커밋시 동기화(flush) 된다.
3. 더티 체킹
1. 1차 캐시에 속한 엔티티가 변경이 감지되면 변경된 내용을 반영하고 트랜잭션 커밋시 DB에 반영한다.
4. 엔티티 식별
1. 1차 캐시 내 엔티티는 항상 식별자로 관리된다.
5. 엔티티 삭제
1. removce 메서드로 1차 캐시에서 제거되며, 트랜잭션 커밋 시 DB에서 삭제된다.

## 쓰기 지연

![image](https://github.com/user-attachments/assets/c6431d46-eeb6-4dd0-a803-aac042917ed1)

![image](https://github.com/user-attachments/assets/eeebaca2-1157-4841-afab-861335153057)

쓰기 지연 SQL 저장소는 **영속성 컨텍스트 내부에 존재하는 저장소**로, INSERT, UPDATE, DELETE와 같은 SQL 명령어를 일시적으로 보관한다. 이는 **여러 SQL 명령어를 한꺼번에 실행해 데이터베이스 접근 횟수를 줄임과 동시에, 한 트랜잭션 내의 SQL문들을 동시에 실행함으로써 데이터 일관성을 보장하려는 목적**에 의한다. 모아진 SQL 명령어는 트랜잭션이 커밋되거나 flush 메서드가 호출될 때 실행된다.

**동작 과정**

1. 엔티티 상태 변경
1. persist(), merge(), remove() 등으로 엔티티 상태가 변경되면 해당 작업이 쓰기 지연 저장소에 SQL문으로 저장된다.
2. 트랜잭션 커밋 또는 flush()
1. 쓰기 지연 저장소에 보관된 SQL문은 트랜잭션이 커밋되거나 flush() 메서드가 호출될 때 데이터베이스로 전송되고 실행된다.

이러한 쓰기 지연은 트랜잭션 위에서만 동작하며, 트랜잭션이 없다면 최초의 persist() 호출부터 예외가 발생한다.