티스토리 뷰

JPA란 무엇일까? 


# 개요

김영한님이 지은신 [자바 ORM표준 JPA 프로그래밍] 책을 보고 정리한 내용입니다. 사용법 보다는 작동원리를 위주로 설명할 것이며 1~4장 내용과 매핑을 중점으로 정리할 생각입니다.  

 


1장. 기존 SQL 작업의 문제점 

1-1. JPA를 사용하는 이유는 뭘까?

SQL을 다루기 위해서는 비지니스 로직보다는 SQL과 JDBC API사용 코드를 작성하는데 많은 시간을 보내야 한다. MyBatis는 이러한 SQL과 JDBC API 사용 코드를 많이 줄일 수  있었지만, 여전히 CRUD를 작성하기 위해 SQL을 반복적으로 작성해야 한다는 단점이 있었다.

 

또한, 가장큰 문제점은 자바는 객체지향이 중심이고 데이터베이스는 테이블이 중심이다. 즉, 객체지향적으로 설계를 하여도 SQL에 적용하기 위해서는 객체지향을 포기하고 테이블에 맞춰어 설계를 해야하며 이 과정이 너무 복자하다는 것이다.

 

이러한 문제를 해결하기 위해 나온것이 ORM이다. Object Rlational Mapping (ORM) 말 그대로 객체 관계 매핑 기술이다. ORM은 객체 모델링과 관계형 데이터베이스의 차이점을 해결해 줄 뿐만아니라, 실행 시점에 SQL을 자동으로 만들어 주기 때문에 많은 코드작업을 획기적으로 줄일수 있다.

 

1-2. JPA가 좋은 기술인가?

[개인적 생각]

JPA는 SQL이 아닌 객체중심으로 개발하기 때문에 생산성과 유지보수가 좋다. JPA는 백엔드 개발자라면 반드시 다를줄 알아야 한다고 생각한다. 하지만 JPA는 위에서 설명한 것 처럼 많은 부분을 자동화 해주는 기술이기 때문에 동작원리를 이해하지 못하면 제대로 활용하기가 힘들다. 그렇기 때문에 사용법보다는 동작원리에 집중하여 게시글을 작성하고자 한다. 

 

1-3. SQL을 직접다룰 때 발생하는 문제점

SQL에 의존적인 개발을 할 수 밖에 없다. 코드를 수정하기 위해서는 일일이 SQL을 수정을 해야한다. 즉, 객체지향이 아닌 SQL에 의존적인 개발을 하고 있기 때문에 데이터 접근 계층을 사용해서 SQL을 숨겨도 어쩔 수 없이 DAO를 열어서 어떤 SQL이 실행되는지 확인해야 한다. 이것은 진정한 의미의 계층 분할이 아니며 논리적으로 엔티티와 아주 강한 의존관계를 가지고 있기 때문에 하나의 컬럼이 추가되더라도 대부분의 SQL 코드가 변경되어야 한다.

 

* 진정한 의미의 계층 분할이 어렵다

* 엔티티를 신뢰할 수 없다.

* SQL에 의존적인 개발을 피하기 어렵다

 

1-4. 패러다임 불일치 

비즈니스 요구사항을 정의한 도메인 모델도 객체로 모델링하면 객체지향 언어가 가진 장점들을 활용할 수 있다. 하지만 문제는 이렇게 정의한  도메인 모델을 저장할 때 문제가 발생한다. 회원이라는 객체 인스턴스를 생성한 후에 이 객체를 메모리가 아닌 어딘가에 영구보관해야 한다. 객체는 속성과 기능을 가진다. 객체의 기능은 클래스에 정의되어 있으므로 객체 인스턴스의 상태인 속성만저장했다가 필요할 때 불러와서 복구하면 된다. 하지만 객체가 단순하지 않고 상속을 받았거나, 다른 객체를 참조하고 있다면 객체 상태를 저장하는 것이 쉽지 않다.

 

객체와 관계형 데이터 베이스는 지향하는 목적이 달라 둘의 기능과 표현 방법이 다르다. 이러한 문제를 '패러다임 불일치 문제'라고 한다. 따라서 객체 구조를 테이블 구조에 저장하는 데는 한계가 있다. 이러한 페러다임 불일치를 해결하기 위해서는 많은 시간과 복잡한 코드가 필요하다. 이러한 문제를 해결해 줄 수 있는 것이 JPA이다. 

 

1-5. 연관관계

* 객체는 참조를 사용해서 다른 객체와 연관관계를 가지고 참조에 접근해서 연관된 관게를 조회한다.

* 테이블은 외래키를 사용해서 다른 테이블과 연관과녜를 가지고 조인을 사용해서 연관된 테이블을 조회한다.

 

* 객체는 참조가 있는 방향으로만 조회할 수 있다.

* 테이블은 외래키 하나로 양방향으로 조회할 수 있다.

    ex) MEMBER JOIN TEAM도 가능하지만 TEAM JOIN MEMBER도 가능하다.

 

1-6. 객체지향 모델링

class Member{
    String id;
    Team team;
}

class Team{
    long id;
}

이처럼 객체지향 모델링을 사용하면 객체를 테이블에 저장하거나 조회하기가 쉽지 않다. Memer객체는 team필드로 연관관계를 맺기 때문인데, 객체 모델은 외래키가 필요없고 단지 참조만 있으면 된다. 반면 테이블은 참조가 필요 없고 외래키만 있으면 된다. 결국, 중간에서 변환을 해주는 역할이 있어야 한다.

 

// 저장
member.getId()
member.getTeam().getId(); //이러한 작업을 해줘야 한다.

// 죄회
SELECT M.*, T.*
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID

 이러한 변환 역활이 있어야 객체 모델링을 데이터베이스에 저장할 수 있다. 이러한 과정들은 모두 패러다임의 불일치를 해결하려고 소모하는 비용이다. 만약 자바 컬렉션에 회원 객체를 저장한다면 이런 비용은 전혀 들지 않는다.

 

JPA는 연관관계와 관련된 패러다임의 불일치 문제를 해결해 준다. 객체를 저장할때 참조를 외래키로 변활하거나, 죄회 할때 외래키를 참조로 변환하는 일을 JPA가 처리해 준다. 

 

1.7 객체 그래프 탐색

객체에서 회원이 소속된 팀을 조회할 때는 참조를 사용해서 연관된 팀을 찾는 것을 '객체 그래프 탐색'이라 한다. 하지만 연관된 객체는 몇개가 있을지 알 수 없기 때문에 마음껏 객체 그래프를 탐색할 수 있어야 한다. 하지만 SQL을 직접 다루면 처음 실행하는 SQL에 따라 객체 그래프를 어디까지 탐색할 수있는지 정할수 있지만, 객체지향 모델링은 비즈니스 로직에 따라 사용하는 객체 그래프가 언제 끊어질지 모를 객체 그래프를 함부로 탐색할 수는 없기 때문이다. 또한 객체 그래프 탐색이 가능한지 알아보려면 데이터 접근 계층인 DAO를 열어서 SQL을 직접 확인해야 한다. 즉, 또 다시 SQL에 의존적인 개발이 되는 것이다.

객체 그래프 탐색

 

Q. 그렇다면 JPA는 이 문제를 어떻게 해결할까?

JPA를 사용하면 객체 그래프를 마음껏 탐색할 수 있다.

member.getOrder().getOrderItem().getItem().getCategory

JPA는 연관된 객체를 사용하는 시점에 적절한 SELECT SQL을 실행한다. 즉, 실제 객체를 사용하는 시점까지 데이터베이스 조회를 미룬다고 해서 '지연로딩'이라 한다.

 

Memer를 사용할 때 마다 Order를 함께 사용하는 것보다, Member를 조회하는 시점에 SQL조인을 사용해서 Member와 Order를 함께 조회하는 것이 효과적이다.

 

Q. 지연로딩이 과연 성능최적화에 답일까?

이것은 8장에 나오는 즉시로딩과 지연로딩에 관한 내용이다. 개발을 하다 보면 대부분 지연로딩을 사용하는 것 같다. 하지만 지연로딩이 성능 최적화에 답은 아니다. 맵핑관계에 따라 다르면 사용빈도 등 다양한 요소에 따라 변화게 된다. 이 부분에 대한 내용은 8장 부분에서 다시 정리하겠다.

 

1.8 JPA 장점

# 생산성

데이터베이스 설계 중심의 패러다임을 객체 설계 중심으로 역전시킬수 있다.

 

# 성능

JPA는 영속성 엔티티를 사용하여 1차캐시 기능을 제공한다. 이 부분도 뒷 내용이지만 간단하게 동일한 SELECT SQL을 두번 호출하면 JPA는 데이터베이스와 2번 통신하지 않고 이전에 조회한 내용을 재사용한다.

 

# 데이터 접근 추상화와 벤더 독립성

데이터베이스는 방언이라는 것이 있다. MySQL과 오라클의 명령어가 다른것을 의미하는데 JPA는 추상화된 데이터 접근 계층을 제공해서 애플리케이션이 특정 데이터베이스에 종속되지 않도록 한다. 즉, MySQL을 사용하다 오라클로 개발환경을 변경하여도 코드 수정이 전혀 필요하지 않는 독립적인 환경을 제공해 준다.

 

1장 정리

객체 모델과 관계형 데이터 베이스 모델을 지향하는 패러다임이 서로 다르다. 이 문제를 해결하기 위해서는 많은 노력과 시간이 필요하다. 또한 객체지향적일 수록 더 복잡해 진다는 것이다. JPA는 이러한 불일치 패러다임을 해결해 주고 객체 모델링을 할 수 있도록 도와준다.

 

 


 

2장. JPA 생성과정 및 동작방법

2-1. 엔티티 매니저 팩토리와 엔티티 매니저

# 엔티티 매니저 팩토리

JPA를 시작하려면 설정정보를 사용해서 엔티티 매니저 팩토리를 생성해야 한다. persistence.xml의  설정 정보를 읽어서 JPA를 동작시키기 위한 기반 객체를 만들고 JPA구현체에 따라서는 데이터베이스 커넥션 풀도 생성하므로 엔티티 매니저 팩토리를 생성하는 비용은 아주 크다. 따라서 엔티티 매니저 팩토리는 애플리케이션 전체에서 딱 한 번만 생성하고 공유해서 사용해야 한다.

 

# 엔티티 매니저

엔티티 매니저 팩토리에서 엔티티 매니저를 생성할 수 있다. 또한 엔티티 매니저를 사용해서 데이터베이스의 CRUD를 할 수 있다. 엔티티 매니저는 내부에서 데이터베이스와 커넥션을 유지하면서 통신하기 때문에 가상의 데이터베이스라고 생각할 수도 있다. 엔티티 매니저는 데이터베이스 커넥션과 밀접한 관계가 있으므로 스레드간에 공유하거나 재사용하면 안된다.

 

2-2. 엔티티 매니저를 이용한 객체 등록 (C)

Member member = new Member();
member.setId("ensu")
member.setUsername("은수")
member.setAge(26)

// 엔티티매니저 등록
em.persist(member)

엔티티 매니저는 객체를 저장하는 가상의 데이터베이스라고 생각해도 된다. 엔티티 매니저의 persist( )메소드에 저장할 객체 엔티티를 넘겨주면 된다. 그러면 JPA는 엔티티를 분석해 다음과 같이 SQL을 만들어 데이터베이스에 저장한다.

INSERT INTO MEMBER (ID, NAME, AGE) VALUES ('ensu','은수',26)

 

2-3. 엔티티 매니저를 이용한 수정 (U)

member.setAge(100);

엔티티 매니저는 em.update( )라는 멧고드를 가지지 않느다. JPA는 단순히 엔티티의 값만 변경하고 어떤 엔티티가 변경되었는지 추적하는 기능을 갖추고 있어  member.set( )만 하더라도 UPDATE SQL 쿼리문이 생성된다.

UPDATE MEMBER
SET AGE=20, NAME='길동'
WHERE ID = 'ensu'

 

2-3. JPQL

JPA는 엔티티 객체를 중심으로 개발하므로 검색할 때도 테이블이 아닌 객체를 대상으로 검색해야 한다. 그런데 테이블이 아닌 엔티티 객체를 대상으로 검색하려면 데이터베이스의 모든 데이터를 애플리케이션으로 불러와서 엔티티 객체로 변경한 다음 검색해야 하는데, 이는 사실상 불가능하다. 애플리케이션에서 필요한 데이터만 데이터베이스에서 불러오려면 검색조건이 포함된 SQL을 사용해야 한다. JPA는 이러한 문제를 JPQL이라는 쿼리 언어로 문제를 해결한다. 

 

JPQL은 엔티티 객체를 대상으로 쿼리를 한다.

SQL은 데이터 베이스 테이블을 대상으로 쿼리한다.

 


 

3장. 영속성 관리

JPA가 제공하는 기능은 크게 2가지가 있다.

1. 엔티티와 테이블을 매핑하는 설계 부분

2. 매핑한 엔티티를 실제 사용하는 부분

 

이번장에서는 2번 매핑한 엔티티를 어떻게 엔티티 매니저를 통해 사용하는지에 대해 알아본다.

 

3-1. 엔티티 매니저 팩토리와 엔티티 매니저

앞에서 간단히 정리하였듯 애플리케이션이 데이터베이스를 하나만 사용하면 JPA는 하나의 엔티티 매니저 팩토리를 생성한다. 엔티티 매니저 팩토리는 생성하는 비용이 비싸기 때문에 애플리케이션 전체가 하나만 공ㅇ하도록 해야 한다. 반면 엔티티 매니저를 생성하는 비용은 거의 들지 않는다. 

 

엔티티 매니저 팩토리는 여러 스레드가 동시에 접근해도 안전하므로 서로 다른 스레드 간에 공유해도 되지만,

엔티티 매니저는 여러 스레드가 동시에 접근하면 동시성 문제가 발생하므로 스레드 간에 절대 공유하면 안 된다.

 

* 엔티티 매니저는 보통 트랜잭션을 시작할때 데이터베이스의 커넥션을 획득하게 된다.

 

3-2. 영속성 컨텍스트란?

JPA에서 가장 중요한 단어는 영속성 컨텍스트이다. 간단하게 '엔티티를 영구히 저장하는 환경'이라는 뜻이다. 엔티티 매니저로 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리한다.

 

em.persist(member);

이 코드를 정확히 해석하면 '엔티티 매니저를 사용해서 회원 엔티티를 영속성 컨텍스트에 저장한다' 이다.

영속성 컨텍스트는 엔티티 매니저를 생성할 때 하나 만들어진다.

 

[정리]

엔티티 매니저는 트랜잭션이 시작할때 데이터베이스와 커넥션을 가진다.

또한 엔티티 매니저는 하나의 영속성 컨텍스를 가질수 있다. 

 

 

3-3. 엔티티의 생명주기

엔티티에는 4가지 상태의 생명주기가 존재한다.

1. 비영속 : 영속성 컨텍스트와 전혀 관계가 없는 상태

2. 영속    : 영속성 컨텍스트에 저장된 상태

3. 준영속 : 영속성 컨텍스트에 저장되었다가 분리된 상태

4. 삭제    : 삭제된 상태

 

# 비영속 

객체를 생성하고 영속성 컨텍스트에 저장하지 않은 상태를 비영속 상태라  한다. 

// 비영속
Member member = new Member();
member.setId("");
member.setUsername("");

//em.persist(member);

영속성 컨텍스트에 저장하는 부분을 주석 처리하였다. 현재 상태를 비영속 상태이며 데이터베이스와는 전혀 관련이 없는 상태이다.

 

# 영속

엔티티 매니저를 통해서 엔티티를 영속성 컨텍스트에 저장한 상태이다.

// 비영속
Member member = new Member();
member.setId("");
member.setUsername("");

// 영속
em.persist(member);

즉, 영속 상태란 영속성 컨텍스트에 의해 관리된다는 뜻이다.

 

# 준영속

영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않으면 준영속 상태이다. 

// 비영속
Member member = new Member();
member.setId("");
member.setUsername("");

// 영속
em.persist(member);

// 준영속
em.detach(member);
em.clear();
em.close();

 

# 삭제

엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제한 상태이다.

em.remove(member);

 

3-4. 영속성 컨텍스트의 특징

# 영속성 컨텍스트와 식별자 값

영속성 컨텍스트는 엔티티를 식별자 값(@Id)으로 구분한다. 따라서 영속 상태는 식별자 값이 반드시 있어야 한다.

 

# 영속성 컨텍스트와 데이터베이스 저장

Q. 영속성 컨텍스트에 엔티티를 저장하면 이 엔티티는 언제 데이터베이스에 저장될까?

JPA는 보통 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 데이트베이스에 반영하는데 이것을 flush(플러시)라고 한다.

 

# 영속성 컨텍스트가 엔티티를 관리의 장점

1. 1차 캐시

2. 동일성 보장

3. 트랜잭션을 지원하는 쓰기 지연

4. 변경 감지

5. 지연 로딩

 

# 엔티티 조회 (1차 캐시)

영속성 컨텍스트는 내부에 캐시를 가지고 있으며, 이를 1차 캐시라고 한다. 간단하게 영속성 컨텍스트 내부에 Map이 하나 있으며 Key는 @Id로 매핑한 식별자고 Value는  엔티티 인스턴스(member)이다. 1차 캐시의 키는 식별자 값이다. 식별자 값은 데이터베이스 기본 키와 매핑되어 있어 영속성 컨텍스트의 데이터를 저장하고 조회하는 모든 기준은 데이터베이스 기본 키 값이다.

 

# 1차 캐시를 이용한 조회

em.find( )를 호출하면 1차 캐시에서 엔티티를 찾고 찾는 엔티티가 1차 캐시에 없으면 데이터베이스에서 조회하여  1차 캐시에 저장한 후에 영속 상태의 엔티티를 반환한다.

 

# 동일성 보장

Member a = em.find(Memer.class,"member1");
Member b = em.find(Memer.class,"member1");

// 동일성 비교
if(a==b){
}

코드와 같이 (a == b) 는 참을 반환한다. 영속성 컨텍스트는 1차 캐시에 있는 같은 엔티티 인스턴스를 반환한다. 따라서 둘은 같은 인스턴스이다.

 

# 쓰기 지연 (엔티티 등록) 

엔티티 매니저는 트랜잭션을 커밋하기 직전까지 데이터베이스에 엔티티를 저장하지 않고 내부 쿼리 저장소에 INSERT SQL을 모아둔다. 그리고 트랜잭션을 커밋할 때 모아둔 쿼리를 데이터베이스에 보내는데 이것을 트랜잭셩을 지원하는 '쓰기 지연'이라 한다. 트랜잭션을 커밋하면 엔티티 매니저는 영속성 컨텍스트를 플러시 한다. 플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업인데 이때 등록,수정,삭제한 엔티티를 데이터베이스에 반영한다.

 

# 변경 감지 (엔티티 수정)

이전에 간단히 말했지만 엔티티 매니저에는 em.update( ) 메소드가 없다. JPA에서는 객체 엔티티에 값만 변경해 주면 변경을 감지하고 자동으로 데이터베이스에 반영을 한다.

 

Q. 변경감지가 어떻게 되는 것일까?

JPA는 엔티티를 영속성 컨텍스트에 보관할 때, 최초의 상태를 복사해서 저장해 두며, 이것을 '스냅샷'이라 한다. 그리고 플러시 시점에 스냅샷과 엔티티를 비교하여 변경된 엔티티를 찾는다. 

1. 커밋을 하면 플러시가 호출된다.

2. 엔티티와 스냅샷을 비교한다.

3. 변경된 내용이 있으면 수정 쿼리를 생성해 쓰기 지연 SQL 저장소에 보낸다.

4. 쓰기 지연 저장소의 SQL을 데이터베이스에 보낸다.

5. 데이터베이스 트랜잭션을 커밋한다.

 

변경감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용된다. 또한 변경감지는 수정시 모든 필드(컬럼)를 업데이트 한다. 모든 컬럼을 업데이트하면 데이터 전송량이 증가한다는 단점은 있지만 모든 컬럼을 사용하면 다음과 같은 장점이 있다.

1. 수정 쿼리가 항상 같다.

2. 로딩 시점에 수정 쿼리를 미리 생성해두고 재사용할 수 있다.

3. 동일한 쿼리를 보내면 데이터베이스는 이전에 한 번 파싱된 쿼리를 재사용할 수 있다.

 

* DynamicUpdate를 사용하면 수정된 데이터만 동적으로 UPDATE SQL을 생성할 수 있다. 하지만 쿼리가 30개가 넘어갈때 적용하는 것이 좋으며 컬럼이 30개가 넘어가면 테이블 설계상 책임 분리가 잘 되었는지 한번 의심해 보아야 한다.

 

 

3-5. 플러시 flush( )

플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다. 플러시의 동작은 다음과 같다.

1. 변경 감지가 동작해서 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교해서 수정된 엔티티를 찾는다.

2. 수정된 엔티티는 수정 쿼리를 만들어 쓰기 지연 저장소에 등록한다.

3. 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송한다.

 

플러시 호출은 다음 조건에 실행된다.

1. em.flush( ) 직접 호출

2. 트랜잭션 커밋시 자동 호출

3. JPQL 쿼리 실행시 자동 호출

단! 식별자를 기준으로 조회하는 find( ) 메소드를 호출할 때는 플러시가 실행되지 않는다.

 

 

3-6. 준영속 

준영속 상태의 엔티티는 영속성 컨텍스트가 제공하는 기능을 사용할 수 없다. 영속 상태의 엔티티를 준영속 상태로 만드는 방법은 크게 3가지다.

1. em.detach( ) : 특정 엔티티만 준영속 상태로 전환

2. em.clear( ) : 영속성 컨텍스트를 초기화

3. em.close( ) : 영속성 컨텍스트를 종료

 

# em.detach( )

em.detach(member)를 호출하면 영속성 컨텍스트에게 더는 해당 컨텍스트를 관리하지 말라는 것이다. 메소드를 호출하는 순간 1차 캐시부터 쓰기 지연 SQL 저장소까지 해당 엔티티를 관리하기 위한 정보가  모두 제거된다. 영속 상태였다가 더는 영속성 컨텍스트가 관리하지 않는 상태를 준영속 상태라 한다. 준영속 상태는 영속성 컨텍스트가 지원하는 어떤 기능도 동작하지 않는다. 

 

# em.clear( )

em.detach( )가 특정 엔티티 하나를 준영속 상태로 만들었다면 em.clear( )는 영속성 컨텍스트를 초기호해서 모든 엔티티를 준영속 상태로 만든다.

 

# em.close( )

영속성 컨텍스트를 종료하면 해당 영속성 컨텍스트가 관리하던 영속 상태의 엔티티가 모두 준영속 상태가 된다.

 

# 준영속 상태의 특징

1. 비영속에 가깝다

영속성 컨텍스가 관리하지 않기 때문에 1차캐시, 쓰기지연, 변경감지, 지연로딩을 포함한 영속성 컨텍스트가 제공하는 어떠한 기능도 동작하지 않는다.

 

2. 식별자 값을 가지고 있다. 

비영속 상태는 식별자 값이 없을 수도 있지만 준영속 상태는 한 번 영속 상태였으므로 반드시 식별자 값을 가지고 있다.

 

3. 지연로딩을 할 수 없다

지연로딩은 실제 객체 대신 프록시 객체를 로딩해두고 해당 객체를 실제 사용할 때 영속성 컨텍스트를 통해 데이터를 불러오는 방법이다. 하지만 영속성 컨텍스트가 더는 관리하지 않으므로 지연로딩 시 문제가 발생한다.

 

# 병합: merge( )

준영속 상태의 엔티티를 다시 영속 상태로 변경하려면 병합하면 된다. merge() 메소드는 준영속 상태의 엔티티를 받아서 그 정보로 새로운 영속 상태의 엔티티를 반환한다. 

 

동작은 다음과 같다.

1. merge( ) 실행

2. 파라미터로 넘어온 준영속 엔티티의 식별자 값으로 1차캐시에서 엔티티를 조회

3. 1차캐시에 엔티티가 없으면 데이터베이스에서 엔티티 조회

4. 조회한 영속 엔티티를 member 엔티티의 값을 채워 넣고 mergeMember라는 새로운 엔티티에 값을 반환

* 준영속 엔티티 상태였던 파라미터 엔티티는 merge( ) 후에도 준영속 상태가 된다.

* 따라서 준영속 엔티티를 참조하던 변수를 영속 엔티티를 참조하도록 변경하는 것이 안전하다.

* merge( )는 비영속 엔티티도 영속 엔티티로 만들 수 있다.

 


 

4장. 엔티티 매핑

4-1. 매핑 종류 

1. 객체와 테이블 매핑 : @Entity, @Table

2. 기본키 매핑 : @Id

3. 필드와 컬럼 매핑 : @Colunm

4. 연관관계 매핑 : @ManyToOne, @JoinColumn

 

# @Entity

JPA 사용을 위해서는 @Entity 어노테이션을 필수로 붙여야 한다. 또한 @Entity를 사용하기 위해서는 다음 조건을 만족해야 한다.

1. 기본 생성자 (필수)

2. final, enum, interface, inner클래스에는 사용할 수 없다.

3. 저장할 필드에 final을 사용하면 안된다.

 

 # @Id

JPA에서 제공하는 데이터베이스 기본 키 생성 전략은 2개가 있다.

1. 직접 할당 : 기본키를 애플리케이션에서 직접 할당

2. 자동 생성 : 대리키를 사용하는 방식

 

직접할당은 개발자가 기본키를 관리한다고 생각하면 되며 자동할당은 데이터베이스나 키 생성 테이블에 의존하는 방식이다. 저는 보통 자동생성의 대표적인 예로 MySQL의 Auto_Increament가 있다. 직접할당은 자연키라 하며, 자동생성은 대리키라고 부른다.

 

책의 저자는 자연키 보다는 대리키 사용을 추천한다. 비즈니스 로직은 언제든지 바뀔수 있으며 외부에 영향을 받을수 있다. 그 예로 주민등록번호 정책이 변경된다면 어떻게 될까? 데이터베이스 테이블 자체가 변경되어야 한다. 하지만 대리키의 경우 외부 영향을 적게 받으며 일관된 방식을 유지하기 때문에 대리키 방식을 추천한다고 한다. 

 

* 자동 생성 전략

자동생성에는 4가지의 방식이 있다. 자동생성 전략이 다양한 이유는 데이터베이스 벤더 마다 지원하는 방식이 다르기 때문이다. 따라서 자동 생성 전략은 데이터베이스에 의존하여 결정된다. 저는 MySQL를 사용하고 있기 때문에 IDENTITY 전략을 조금더 정리하고 가도록 하겠습니다.

 

1. IDENTITY : 기본키 생성을 데이터베이스에 위임

2. SEQUENCE : 데이터베이스 시퀀스를 사용해 기본키 할당

3. TABLE : 키 생성 테이블 사용

4. AUTO : 디폴트 값 ( 데이터베이스 벤더에 따라 자동으로 결정) 

 

* IDENTITY 전략과 최적화

IDENTITY전략은 데이터를 데이터베이스에 INSERT한 후에 기본키 값을 조회할 수 있다. 따라서 엔티티에 식별자 값을 할당하려면 JPA는 추가로 데이터베이스를 조회해야 한다. 엔티티가 영속 상태가 되려면 식별자가 반드시 필요하다. 그런데 IDENTITY 식별자 생성 전략은 데이터베이스에 저장해야 식별자를 구할 수 있으므로 em.persist( )를 호출하는 즉시 INSERT SQL이 데이터베이스에 전달된다. 때문에 이 전략은 트랜잭션을 지원하는 쓰기 지연이 동작하지 않는다. 

 

Q. IDENTITY 전략을 사용하는 엔티티를 UserRepository.save(member); 하면 쓰기 지연 로딩이 적용되지 않고 바로 데이터베이스에 저장되는 것인가? 

Q. 쓰기 지연 로딩이 적용되지 않고 저장시 이전에 쓰기지연에 저장된 값은 어떻게 되는 것인가? 같이? 따로?

 

JDBC3에 추가된 Statement.getGeneratedKeys( )를 사용하면 데이터를 저장하면서 동시에 생성된 기본키 값도 얻어 올 수 있다. 하이버네이트는 이 메소드를 사용해서 데이터베이스와 한번만 통신한다. 

 

Q. Statement.getGeneratedKeys( ) 이것은 개발자가 직접 사용해야 하는 것일까?

 

 

 

 

 

 

 

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/01   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함