diff --git "a/\354\235\264\352\260\200\354\227\260/vol 2./1\354\236\245/IOC\354\231\200 DI.md" "b/\354\235\264\352\260\200\354\227\260/vol 2./1\354\236\245 IOC\354\231\200 DI.md" similarity index 100% rename from "\354\235\264\352\260\200\354\227\260/vol 2./1\354\236\245/IOC\354\231\200 DI.md" rename to "\354\235\264\352\260\200\354\227\260/vol 2./1\354\236\245 IOC\354\231\200 DI.md" diff --git "a/\354\235\264\352\260\200\354\227\260/vol 2./2\354\236\245 \353\215\260\354\235\264\355\204\260 \354\225\241\354\204\270\354\212\244 \352\270\260\354\210\240.md" "b/\354\235\264\352\260\200\354\227\260/vol 2./2\354\236\245 \353\215\260\354\235\264\355\204\260 \354\225\241\354\204\270\354\212\244 \352\270\260\354\210\240.md" new file mode 100644 index 0000000..cd36ad8 --- /dev/null +++ "b/\354\235\264\352\260\200\354\227\260/vol 2./2\354\236\245 \353\215\260\354\235\264\355\204\260 \354\225\241\354\204\270\354\212\244 \352\270\260\354\210\240.md" @@ -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 merge(T entity); + + public void remove(Object entity); + + public T find(Class 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() 호출부터 예외가 발생한다.