Post

[JPA] JAVA ORM 표준 JPA

[JPA] JAVA ORM 표준 JPA

JPA(Java Persistence API)

SQL 없이 객체를 데이터베이스에 직접 저장할 수 있게 하고, 객체와 관계형 데이터베이스를 명확히 구분해준다. 즉, 중간에서 매핑 역할을 수행하는 ORM(Object-Relational Mapping) 기술의 표준 인터페이스이다.

객체지향과 관계형 데이터베이스의 패러다임 불일치를 해결해준다.

JPA를 사용하는 이유

유지보수 용이

  • 필드 추가/변경 시 SQL을 수정할 필요가 없고 엔티티 클래스만 수정하면 자동으로 반영

생산성 향상

  • CRUD SQL 이나, 반복적인 JDBC 코드를 개발자가 작성할 필요가 없다.

객체지향적 개발 가능

  • SQL 중심이 아닌 객체 중심으로 개발해 비즈니스 로직에 집중할 수 있다.

데이터베이스 독립성

  • 특정 DB에 종속되지 않고, DB 변경 시 설정만 바꾸면 된다.

패러다임 불일치 해결

  • 상속, 연관관계, 객체 그래프 탐색 등 객체지향 기능을 자연스럽게 사용 가능하다.
  • 복잡한 JOIN 로직을 객체 참조로 대체

성능 최적화

  • 1차 캐시, 지연 로딩, 쓰기 지연 등 다양한 최적화 기능 제공

SQL 대신 객체로 작업하면서 생산성을 높이고, 유지보수를 쉽게 하기 위해 사용한다.


ORM(Object-Relational Mapping)

객체와 관계형 데이터베이스의 데이터를 자동으로 매핑해주는 기술, 객체지향 프로그래밍의 객체와 관계형 데이터베이스의 테이블 사이의 불일치를 해결해주는 다리 역할을 한다.

1
2
3
4
5
객체(Object) ↔ 테이블(Table)

필드(Field) ↔ 컬럼(Column)

인스턴스(Instance) ↔ 행(Row)
  • JPA = 자바의 ORM 기술 표준 인터페이스 (명세)
  • Hibernate, EclipesLink = JPA를 구현한 구현체

JPA는 ‘무엇을 해야 하는지(What)’ , Hibernate 같은 구현체가 ‘어떻게 할 것인가(How)’ 를 구현


JPA의 구동 방식

JPA는 persistence.xml 설정 파일을 읽어서 EntityManagerFactory를 생성한다.

1
2
3
4
5
6
7
persistence.xml 읽기
       ↓
EntityManagerFactory 생성 (앱 전체에서 1개만)
       ↓
EntityManager 생성 (요청마다 생성/삭제)
       ↓
트랜잭션 단위로 작업 수행

EntityManagerFactory

  • 애플리케이션 로딩 시점 1개만 생성해 전체 공유
  • 생성 비용이 크기 때문에 1개만 생성해 재사용

EntityManager

  • 요청(트렌젝션)마다 생성하고 사용 후 버림
  • 쓰레드 간 공유 금지 (동시성 이슈)
  • 데이터베이스 커넥션과 밀접한 관계

사용 흐름

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
// 1. EntityManagerFactory 생성 (앱 시작 시 1번)
EntityManagerFactory emf = 
    Persistence.createEntityManagerFactory("hello");

// 2. EntityManager 생성 (요청마다)
EntityManager em = emf.createEntityManager();

// 3. 트랜잭션 획득
EntityTransaction tx = em.getTransaction();
tx.begin();

try {
    // 비즈니스 로직 실행
    Cat cat = new Cat();
		cat.setId(1L);
		cat.setCatname("생강이");
    
    tx.commit();  // 커밋
} catch (Exception e) {
    tx.rollback();  // 롤백
} finally {
    em.close();  // EntityManager 종료
}

emf.close();  // 앱 종료 시
  • JPA의 모든 데이터 변경은 트랜잭션 안에서 실행해야 한다.
  • EntityManager는 쓰레드 간 공유 금지 사용 후 반드시 close()
  • Spring Data JPA 사용 시 EntityManagerFactory, EntityManager 생성을 Spring이 자동으로 처리해 개발자는 @Repository, @Transactional 만 사용

영속성 컨텍스트

엔티티를 영구 저장하는 환경

em.persist(entity); 는 엔티티를 DB에 저장하는게 아니라, 영속성 컨텍스트에 저장하는 것이다.

  • 논리적인 개념으로 눈에 보이지 않음
  • EntityManager를 통해 접근하고 EntityManager 생성 시 1:1로 영속성 컨텍스트가 생성

엔티티의 생명주기

비영속 (new/transient)

  • 영속성 컨텍스트와 전혀 관계없는 새로운 상태
1
2
3
4
Cat cat = new Cat();
cat.setId(1L);
cat.setCatname("생강이");
// 아직 영속성 컨텍스트에 없음

영속 (managed)

  • 영속성 컨텍스트에 관리되는 상태
1
em.persist(cat); //영속성 컨텍스트에 저장됨 (아직 데이터베이스에 저장 안됨)

준영속 (detached)

  • 영속성 컨텍스트에 저장되었다가 분리된 상태
1
2
3
em.detach(cat);  // 특정 엔티티만 준영속 상태로
em.clear();         // 영속성 컨텍스트 완전히 초기화
em.close();         // 영속성 컨텍스트 종료

삭제 (removed)

  • 삭제된 상태
1
em.remove(cat);  //객체를 삭제한 상태

영속성 컨텍스트의 장점

1차 캐시

  • 영속성 컨텍스트 내부에 1차 캐시가 존재한다.
1
2
3
4
5
6
7
8
// 1차 캐시에 저장
Cat cat = new Cat();
cat.setId(1L);
cat.setCatname("생강이");
// 아직 영속성 컨텍스트에 없음

// 1차 캐시에서 조회 (select sql 실행안됨)
Cat findCat = em.find(Cat.class, 1L);
  • 같은 트랜잭션 안에서는 같은 엔티티를 반환 (동일성 보장)
  • DB 조회 횟수를 줄여 성능 향상

동일성(identity) 보장

1
2
3
4
Cat a = em.find(Cat.class, 1L);
Cat b = em.find(Cat.class, 1L);

System.out.println(a == b);  // true
  • 1차 캐시로 반복 가능한 읽기 등급의 트랜잭션 격리 수준을 애플리케이션 차원에서 제공

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

1
2
3
4
5
6
7
8
EntityTransaction tx = em.getTransaction();
tx.begin();

em.persist(catA);
em.persist(catB);
// 여기까지 INSERT SQL을 DB에 보내지 않음

tx.commit();  // 커밋하는 순간 DB에 INSERT SQL을 모아서 보냄
  • INSERT SQL을 모아서 한번에 전송 → 성능 최적화

변경 감지 (Dirty Checking)

1
2
3
4
5
6
7
8
9
10
11
12
13
EntityTransaction tx = em.getTransaction();
tx.begin();

// 영속 엔티티 조회
Cat catA = em.find(Cat.class, 1L);

// 영속 엔티티 데이터 수정
catA.setCatname("여름이");
catA.setAge(3);

// em.update(member) 이런 코드 필요 없음!

tx.commit();  // 커밋 시점에 자동으로 UPDATE SQL 실행
  • 트랜잭션 커밋 시 em.flush() 자동 호출
  • 엔티티와 스냅샷(최초상태) 비교
  • 변경된 엔티티가 있으면 UPDATE SQL 생성
  • UPDATE SQL을 DB 에 전송
  • 트랜잭션 커밋

지연로딩

  • 연관된 엔티티를 실제 사용할 때까지 조회를 미루는 기능

flush

영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 것.

flush 발생 시:

  • 변경 감지 (Dirty Checking)
  • 수정된 엔티티를 쓰기 지연 SQL 저장소에 등록
  • 쓰기 지연 SQL 저장소의 쿼리를 DB에 등록 (등록,수정,삭제)

flush 사용 법:

1
2
3
em.flush();        // 직접 호출
tx.commit();       // 트랜잭션 커밋 시 자동 호출
JPQL 쿼리 실행;     // JPQL 쿼리 실행 시 자동 호출
  • flush는 영속성 컨텍스트를 비우지 않음
  • 영속성 컨테그스트의 변경 내용을 DB에 동기화 하는 것
  • 트랜잭션이라는 작업 단위가 중요 → 커밋 직전에만 동기화 하면 됨.

기본키 매핑

직접 할당

1
2
@Id
private Long id;
  • 애플리케이션에서 직접 ID값을 설정
  • @GeneratedValue 없이 @Id 만 사용

자동 생성(@GeneratedValue)

1
2
3
@Id
@GeneratedValue(strategy = GenerationType.XXX)
private Long id;
  • 데이터베이스가 자동으로 ID생성
  • strategy로 생성 전략 선택

기본키 생성 전략 (strategy)

IDENTITY 전략

기본키 생성을 데이터베이스에 위임

1
2
3
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
  • em.persist() 호출 시점에 즉시 INSERT SQL 실행
  • DB에 INSERT를 해야만 ID 값을 알 수 있음
  • 쓰기 지연 불가능

SEQUENCE 전략

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

1
2
3
4
5
6
7
8
9
10
11
12
13
@Entity
@SequenceGenerator(
    name = "cat_seq_generator",
    sequenceName = "cat_seq",
    initialValue = 1, 
    allocationSize = 50
)
public class Cat {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                    generator = "cat_seq_generator")
    private Long id;
}
  • em.persist() 호출 시점에 시퀀스에서 값을 조회
  • 조회한 값을 엔티티에 할당 후 영속성 컨텍스트에 저장
  • 트랜잭션 커밋 시점에 INSERT SQL 실행 (쓰기 지연 가능)
  • allocationSize로 성능 최적화 가능 (기본값 50)

TABLE 전략

키 생성 전용 테이블을 만들어 시퀀스를 흉내내는 전략

  • 모든 DB에서 사용 가능
  • 성능 이슈로 실무에서 잘 사용하지 않음

AUTO 전략 (기본값)

DB 방언(Dialect) 에 따라 자동으로 선택

1
2
3
@Id
@GeneratedValue(strategy = GenerationType.AUTO) // 또는 생략 가능
private Long id;
  • 데이터베이스를 변경해도 코드 수정 불필요
  • MySQL → IDENTITY
  • Oracle → SEQUENCE
This post is licensed under CC BY 4.0 by the author.