> For the complete documentation index, see [llms.txt](https://incheol-jung.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://incheol-jung.gitbook.io/docs/study/jpa/2020-04-07-jpa-chap1.md).

# 1장 JPA 소개

## SQL을 직접 다룰 때 발생하는 문제점

`SQL`을 직접 다룰 때의 문제점을 알아보기 위해 `자바`와 `관계형 데이터베이스`를 사용해서 회원 관리 기능을 개발해보자.

#### 먼저 회원을 조회하는 기능을 개발한다.

* 회원 조회용 SQL을 작성한다.

```java
SELECT MEMBER_ID,  NAME FROM MEMBER M WHERE MEMBER_ID = ?
```

* JDBC API를 사용해서 SQL을 실행한다.

```java
ResultSet rs = stmt.executeQuery(sql);
```

* 조회 결과를 Member 객체로 실행한다.

```java
String memberId = rs.getString("MEMBER_ID");
String name = rs.getString("NAME");

Member member = new Member();
member.setMemberId(memberId);
member.setName(name);
...
```

#### 이번엔 회원 등록 기능을 개발한다.

* 회원 등록용 SQL을 작성한다.

```java
String sql = "INSERT INTO MEMBER(MEMBER_ID, NAME) VALUES(?,?)";
```

* 회원 객체의 값을 꺼내서 등록 SQL에 전달한다.

```java
pstmt.setString(1, member.getMemberId());
pstmt.setString(2, member.getName());
```

* JDBC API를 사용해서 SQL을 실행한다.

```java
pstmt.executeUpdate(sql);
```

회원 객체를 데이터베이스가 아닌 `자바 컬렉션`에 보관한다면 어떨까? 컬렉션은 다음 한 줄로 객체를 저장할 수 있다.

```java
list.add(member);
```

따라서 개발자가 `객체지향 애플리케이션`과 `데이터베이스` 중간에서 `SQL`과 `JDBC API`를 사용해서 `변환` 작업을 직접 해주어야 한다.

문제는 객체를 데이터베이스에 `CRUD`하려면 너무 많은 `SQL`과 `JDBC API`를 코드로 작성해야 한다는 점이다. `데이터 접근 계층(DAO)`을 개발하는 일은 이렇듯 지루함과 반복의 연속이다.

### SQL에 의존적인 개발

회원 객체를 관리하는 `MemberDAO`를 완성하고 애플리케이션의 나머지 기능도 개발을 완료했다. **그런데 갑자기 회원의 연락처도 함께 저장해 달라는 `요구사항`이 추가되었다.**

#### 등록 코드 변경

* 회원 클래스에 연락처 필드 추가

```java
public class Member {
  private String memberId;
  private String name;
  private String tel; // 추가
  ...
}
```

* 등록 SQL을 수정

```java
String sql = "INSERT INTO MEMBER(MEMBER_ID, NAME, TEL) VALUES(?,?,?)";
```

* 회원 객체의 연락처 값을 꺼내서 등록 SQL에 전달한다.

```java
pstmt.setString(3, member.getTel());
```

#### 조회 코드 변경

다음으로 회원 조회 화면에서 모든 연락처의 값이 `null`로 출력되었다. **생각해보니 조회 `SQL`에 연락처 컬럼을 추가하지 않았다.**

* 조회용 SQL을 수정

```java
SELECT MEMBER_ID, NAME, TEL FROM MEMBER WHERE MEMBER_ID = ?
```

* 연락처의 조회 결과를 Member 객체에 추가로 매핑한다.

```java
String tel = rs.getString("TEL");
member.setTel(tel);
```

#### 연관된 객체

회원은 어떤 한 팀에 필수로 소속되어야 한다는 요구사항이 추가되었다. `Member` 객체에 `team` 필드가 추가되어 있었다. 그러나 코드를 실행해보니 `member.getTeam()`의 값이 항상 `null`이 나오고 있었다.

`memberDAO` 코드를 열어보니 회원을 출력할 때 사용하는 `find()` 메소드는 회원만 조회하는 `SQL`을 그대로 유지하였다. 그래서 `findWithTeam()` 메서드를 추가하였다.

* 연관 팀 정보를 가져오는 SQL 추가

```sql
SELECT M.MEMBER_ID, M.NAME, M.TEL, T.TEAM_ID, T.TEAM_NAME
FROM MEMBER M
JOIN TEAM T
  ON M.TEAM_ID = T.TEAM_ID
```

위의 사례에서 보듯이 `Member` 객체가 연관된 `Team` 객체를 사용할 수 있을지 없을지는 전적으로 사용하는 `SQL`에 달려 있다. 이런 방식의 가장 큰 문제는 `데이터 접근 계층`을 사용해서 `SQL`을 숨겨도 어쩔 수 없이 `DAO`를 열어서 어떤 `SQL`이 실행 되는지 확인해야 한다는 점이다.

이렇듯 `SQL`에 모든 것을 의존하는 상황에서는 개발자들이 `엔티티`를 신뢰하고 사용할 수 없다. **대신에 `DAO`를 열어서 어떤 `SQL`이 실행되고 어떤 객체 들이 함께 조회 되는지 일일이 확인해야 한다.**

#### 앞의 문제점을 요약하면 다음과 같다.

* **진정한 의미의 계층 분할이 어렵다.**
* **엔티티를 신뢰할 수 없다.**
* **SQL에 의존적인 개발을 피하기 어렵다.**

### JPA와 문제 해결

`JPA`를 사용하면 객체를 데이터베이스에 저장하고 관리할 때, 개발자가 직접 `SQL`을 작성하는 것이 아니라 `JPA`가 제공하는 `API`를 사용하면 된다. 그러면 `JPA`가 개발자 대신에 적절한 `SQL`을 생성해서 데이터베이스에 전달한다.

이전에 구현하였던 애플리케이션의 기능을 JPA를 사용해서 구현 해보자.

* 조회 기능

```java
String memberId = "helloId";
Member member = jpa.find(Member.class, memberId); // 조회
```

* 수정 기능

```java
Member member = jpa.find(Member.class, memberId);
member.setName("이름변경"); // 수정
```

* 연관된 객체 조회

```java
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam(); // 연관된 객체 조회
```

## 패러다임의 불일치

비즈니스 요구사항을 정의한 도메인 모델도 객체로 `모델링`하면 객체지향 언어가 가진 `장점`들을 활용할 수 있다. 문제는 이렇게 정의한 `도메인 모델`을 저장할 때 발생한다.

* `객체의 기능`은 클래스에 정의되어 있으므로 객체 `인스턴스`의 `상태`인 속성만 저장 했다가 필요할 때 불러 와서 복구하면 된다.
* `관계형 데이터베이스`는 데이터 중심으로 구조화되어 있고, `집합적인 사고`를 요구한다. 그리고 객체 지향에서 이야기하는 `추상화`, `상속`, `다형성` 같은 개념이 없다.

객체와 관계형 데이터베이스는 `지향하는 목적`이 서로 다르므로 둘의 `기능`과 `표현 방법`도 다르다. 이것을 객체와 관계형 데이터베이스의 `패러다임 불일치 문제`라 한다. 따라서 `객체 구조`를 `테이블 구조`에 저장하는 데는 한계가 있다.

문제는 이런 객체와 관계형 데이터베이스 사이의 패러다임 불일치 문제를 해결하는 데 `너무 많은 시간`과 `코드`를 `소비`하는 데 있다.

### 상속

**객체는 상속 이라는 기능을 가지고 있지만 테이블은 상속 이라는 기능이 없다.**

![jpa1](https://user-images.githubusercontent.com/2491418/78592088-79ed5c80-787f-11ea-8174-8afcfcc8ff7a.png)

#### JDBC를 이용한 상속 구현

JDBC API를 사용해서 상속 관계의 객체를 저장 하려면 `부모 객체`에서 `부모 데이터`만 꺼내서 INSERT SQL을 작성하고 `자식 객체`에서 `자식 데이터`만 꺼내서 INSERT SQL을 별도로 작성해야 한다.

```java
INSERT INFO ITEM ...
INSERT INTO ALBUM ...
```

#### JPA를 이용한 상속 구현

JPA는 상속과 관련된 패러다임의 불일치 문제를 개발자 대신 해결해준다. 개발자는 마치 `자바 컬렉션`에 객체를 저장 하듯이 `JPA`에게 객체를 저장하면 된다.

```java
jpa.persist(album);
```

### 연관관계

객체는 참조를 사용해서 다른 객체와 연관관계를 가지고 `참조`에 `접근`해서 연관된 객체를 조회한다. 반면에 테이블은 `외래 키`를 사용해서 다른 테이블과 연관관계를 가지고 `조인`을 사용해서 연관된 테이블을 조회한다.

* 테이블에 맞춘 객체 모델

```java
class Member {
  String id; // MEMBER_ID 컬럼 사용
  Long temId; // TEAM_ID FK 컬럼 사용
  String username; // USERNAME 컬럼 사용
}

class Team {
  Long id; // TEAM_ID PK 사용
  String name; // NAME 컬럼 사용
}
```

그런데 여기서 `TEAM_ID` 외래 키의 값을 그대로 보관하는 `teamId` 필드에는 문제가 있다. 객체는 연관된 객체의 `참조`를 보관해야 다음처럼 참조를 통해 연관된 객체를 찾을 수 있어야 하는데 하지 못하게 된다.

```java
Team team = member.getTeam();
```

**이런 방식을 따르면 좋은 객체 모델링은 기대하기 어렵고 결국 객체 지향의 특징을 잃어버리게 된다.**

{% hint style="info" %}
**엔티티를 직접 참조하는 것과 간접 참조하는 것에 대한 장단점이 무엇이 있을까?**

직접 참조

* 장점 : 연관된 데이터를 `한번에` 추출할 수 있다.
* 단점 : 연관된 데이터에 대한 수정이 발생할 경우 `영향의 범위`가 커질 수 있다.

간접 참조

* 장점 : 복잡도를 낮출 수 있고, `응집도`를 높이고 `결합도`를 낮출 수 있다.

* 단점 : 연관된 데이터를 한번에 추출 하려면 구현해야 하는 `로직`이 복잡하다.
  {% endhint %}

* 참조를 사용하는 객체 모델

```java
class Member {
  String id; // MEMBER_ID 컬럼 사용
  Long temId; // TEAM_ID FK 컬럼 사용
  Team team; // 참조로 연관관계를 맺는다.
}

class Team {
  Long id; // TEAM_ID PK 사용
  String name; // NAME 컬럼 사용
}
```

#### JDBC를 이용한 연관 관계 구현

만약 JDBC로 저장하는 로직을 만들기 위해서는 team 필드를 TEAM\_ID `외래 키` 값으로 변환해야 한다.

```java
member.getId(); // MEMBER_ID PK에 저장
member.getTeam().getId(); // TEAM_ID FK에 저장
member.getUsername(); // USERNAME 컬럼에 저장
```

또는 조회하는 로직에도 객체를 생성하고 연관관계를 설정해서 반환하는 로직이 필요하다.

```java
public Member find(String memberId) {
  // SQL 실행
  ...
  Member member = new Member();
  ...

  // 데이터베이스에서 조회한 회원 관련 정보를 모두 입력
  Team team = new Team();
  ...
  // 데이터베이스에서 조회한 팀 관련 정보를 모두 입력

  // 회원과 팀 관계 설정
  member.setTeam(team);
  return member;
}
```

#### JPA와 연관관계 구현

개발자는 `회원`과 `팀`의 관계를 설정하고 `회원 객체`를 저장하면 된다. `JPA`는 `team`의 참조를 외래 키로 변환해서 적절한 INSERT SQL을 데이터베이스에 전달한다.

```java
member.setTeam(team);
jpa.persist(member);
```

객체를 조회할 때 외래 키를 참조로 변환하는 일도 JPA가 처리 해준다.

```java
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam();
```

### 객체 그래프 탐색

`SQL`을 직접 다루면 처음 실행하는 `SQL`에 따라 객체 그래프를 어디까지 탐색할 수 있는지 정해진다. 이것은 객체지향 개발자에겐 너무 큰 제약이다. 왜냐하면 비즈니스 로직에 따라 사용하는 객체 그래프가 다른데 언제 끊어질지 모를 `객체 그래프`를 함부로 탐색할 수는 없기 때문이다.

결국, 어디까지 객체 그래프 탐색이 가능한지 알아보려면 데이터 접근 계층인 `DAO`를 열어서 `SQL`을 직접 확인해야 한다.

```java
class MemberService {
  ...
  public void process() {
    Member member = memberDAO.find(memberId);
    member.getTeam(); // member->team 객체 그래프 탐색이 가능한가?
    member.getOrder().getDelivery(); // ???
  }
}
```

#### JPA와 객체 그래프 탐색

JPA를 사용하면 연관된 객체를 신뢰하고 마음껏 조회할 수 있다. 이 기능은 실제 객체를 사용하는 기점까지 데이터베이스 조회를 미룬다고 해서 `지연 로딩`이라 한다.

```java
// 처음 조회 시점에 SELECT MEMBER SQL
Member member = jpa.find(Member.class, memberId);

Order order = member.getOrder();
order.getOrderDate(); // Order를 사용하는 시점에 SELECT ORDER SQL
```

여기서 마지막 줄의 `order.getOrderDate()` 같이 실제 `Order` 객체를 사용하는 시점에 JPA는 데이터베이스에서 `ORDER` 테이블을 조회한다.

### 비교

데이터베이스는 기본 키의 값으로 각 로우(row)를 구분한다. 반면에 객체는 `동일성(indentity)`비교와 `동등성(equality)` 비교라는 두 가지 비교 방법이 있다.

* **동일성 비교는 == 비교다. 객체 인스턴스의 주소 값을 비교한다.**
* **동등성 비교는 equals() 메소드를 사용해서 객체 내부의 값을 비교한다.**

#### JDBC로 구현한 비교

```java
class MemberDAO {
  public Member getMember(String memberId) {
    String sql = "SELECT * FROM MEMBER WHERE MEMBER_ID + ?";
    ...

    // JDBC API, SQL 실행
    return new Member(...);
  }
}

String memberId = "100";
Member member1 = memberDAO.getMember(memberId);
Member member2 = memberDAO.getMember(memberId);

member1 == member2; // false
```

**데이터베이스의 같은 로우를 조회했지만 객체의 동일성 비교에는 실패한다.**

#### JPA로 구현한 비교

JPA는 같은 트랜잭션일 때 같은 객체가 조회되는 것을 보장한다. 그러므로 다음 코드에서 member1과 member2는 동일성 비교에 성공한다.

```java
String memberId = "100";
Member member1 = jpa.find(Member.class,memberId);
Member member2 = jpa.find(Member.class,memberId);

member1 == member2; // true
```

### 정리

`객체 모델`과 `관계형 데이터베이스 모델`은 지향하는 `패러다임`이 서로 다르다. **더 어려운 문제는 객체 지향 애플리케이션 답게 정교한 객체 모델링을 할수록 패러다임의 불일치 문제가 더 커진다는 점이다.** 이는 결국 객체 모델링은 힘을 잃고 점점 `데이터 중심의 모델`로 변해간다. JPA는 패러다임의 불일치 문제를 해결 해주고 정교한 객체 모델링 유지하게 도와준다.

> 데이터 주도 설계와 도메인 주도 설계의 장단점은 무엇일까?

## JPA란 무엇인가?

`JPA(Java Persistence API)`는 자바 진영의 ORM 기술 표준이다.

#### 그렇다면 ORM이란 무엇일까?

`ORM(Object-Relational Mapping)`은 이름 그대로 객체와 관계형 데이터베이스를 `매핑` 한다는 뜻이다. ORM 프레임워크는 객체와 테이블을 매핑 해서 패러다임의 불일치 문제를 개발자 대신 해결해준다.

따라서 객체 측면에서는 정교한 객체 모델링을 할 수 있고 관계형 데이터베이스는 데이터베이스에 맞도록 모델링하면 된다. 그리고 둘을 어떻게 매핑 해야 하는지 매핑 방법만 ORM 프레임워크에게 알려주면 된다.

### 왜 JPA를 사용해야 하는가?

* 생산성

  이전에 `DAO`에서 작업하던 지루하고 반복적인 일은 `JPA`가 대신 처리 해준다. 이런 기능들을 사용하면 데이터베이스 설계 중심의 패러다임을 `객체 설계 중심`으로 역전시킬 수 있다.
* 유지보수

  이전엔 `엔티티` 필드 하나만 수정해도 관련된 `DAO` 로직의 `SQL`문을 모두 변경해야 했다. 반면에 `JPA`는 대신 처리해주므로 필드를 추가하거나 삭제해도 수정해야 할 코드가 줄어든다.
* 패러다임의 불일치 해결
* 성능

  JPA는 애플리케이션과 데이터베이스 사이에 동작하여 `최적화` 관점에서 시도해 볼 수 있는 것들이 많다. 예를 들어 동일한 조건으로 조회 했을 경우엔 SELECT SQL을 한 번만 데이터베이스에 전달하고 두 번째 조회한 회원 객체는 `재사용`할 수 있다.
* 데이터 접근 추상화와 벤더 독립성

  관계형 데이터베이스는 같은 기능도 벤더 마다 사용법이 다른 경우가 많다. 단적인 예로 `페이징 처리`는 데이터베이스마다 달라서 사용법을 각각 배워야 한다. 결국 애플리케이션은 데이터베이스에 `종속`되어 변경 하기는 매우 어렵다. `**JPA`는 추상화된 데이터 접근 계층을 제공해서 애플리케이션이 특정 데이터베이스 기술에 종속되지 않도록 할 수 있다.\*\*

### ORM에 대한 궁금증과 오해

{% hint style="info" %}
**JPA를 사용하면 성능이 느리지 않나요?**

`JPA`는 다양한 성능 `최적화` 기능을 제공해서 잘 이해하고 사용하면 `SQL`을 직접 사용할 때보다 더 좋은 성능을 낼 수도 있다. 하지만 JPA를 잘 이해하지 못하고 사용하면 **`N+1`** 같은 문제로 인해 심각한 성능 저하가 발생할 수 있다.

> JPA N+1 문제란 무엇인가? 해결 방법은 또한 무엇인가?
> {% endhint %}

{% hint style="info" %}
**통계 쿼리처럼 매우 복잡한 SQL은 어떻게 하나요?**

JPA는 통계 쿼리 같이 복잡한 쿼리보다는 `실시간 처리용 쿼리`에 더 최적화되어 있다. 따라서 통계용은 `네이티브 SQL`을 사용하거나 `마이바티스`나 스프링의 `jdbcTemplate` 같은 SQL 매퍼 형태의 프레임워크를 혼용하는 것도 좋은 방법이다.
{% endhint %}
