JPA(Java Persistence API)
- 자바 진영에서 ORM(Object Relational Mapping)기술의 표준사양(명세-Specification)(= 인터페이스)
=> 실제 구현된 것들이 아닌, 구현된 클래스들과 매핑해주기 위해 사용되는 기술 모음 - 현재는 Jakarta Persistence라고 불린다.
Hibernate ORM(Object Relational Mapping)
- JPA 인터페이스를 구현한 구현체 중 하나
- 애플리케이션의 Class와 관계형 데이터베이스의 Table을 자동으로 매핑해 주는 기술
JPA in Data Access Layer
- JPA는 데이터 엑세스 계층에서 상단에 위치한다.
=> 데이터 CRUD 작업은 JPA를 거쳐 JPA의 구현체인 Hibernate ORM을 통해 이루어진다. - Hibernate ORM은 내부적으로 JDBC API를 이용해 DB에 접근한다.
Persistence Context
Persistence(영속성, 지속성)
- 무언가를 금방 사라지게 하지 않고 오래 지속되게 하는 성질
Persistence Context(영속성 컨텍스트)
- 테이블과 매핑되는 엔티티 객체 정보가 애플리케이션 내에서 오래 지속되도록 보관해 두는 곳
- 보관된 엔티티 객체 정보는 DB 테이블에 데이터를 CRUD할 때 사용
1차 캐시
- 엔티티 정보를 Persistence Context에 저장(persist)하기 위해 JPA API를 사용했을 때 엔티티 정보가 먼저 저장되는 곳
쓰기지연 SQL 저장소
- 한 트랜잭션이 commit되기 전까지 SQL 쿼리를 별도로 저장해 뒀다가 한번에 보낼 수 있는 곳
JPA API를 통한 Persistence Context 이해
✔️의존 라이브러리 추가
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
✔️JPA 설정(application.yml)
spring:
h2:
console:
enabled: true
path: /h2
datasource:
url: jdbc:h2:mem:test
jpa:
hibernate:
ddl-auto: create # 스키마 자동 생성
show-sql: true # SQL 쿼리 출력
1. JPA는 ddl-auto 설정을 통해 데이터베이스 테이블이 자동으로 생성되게 할 수 있다(JDBC는 수작업 해야 함).
2. JPA의 show-sql 설정을 통해 실행되는 SQL 쿼리를 로그로 확인할 수 있다.
=> 동작과정 좀 더 쉽게 이해가능!
☑️ Configuration 생성
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
@Configuration
public class JpaConfig {
...
@Bean
public CommandLineRunner testJpaRunner(EntityManagerFactory emFactory) {
...
return args -> {};
}
}
- @Configuration 애너테이션을 추가해 Bean 검색대상이 되게 해서 return 객체를 Spring Bean 객체로 추가해준다.
- CommandLineRunner 인터페이스를 @Bean 애너테이션을 활용해서 익명클래스로 선언했다.
- CommandLineRunner 객체를 람다 표현식으로 정의해주면 애플리케이션 부트스트랩 과정이 완료된 시점에 람다 표현식에 정의한 코드가 실행된다.
=> Spring Boot Application이 구동될 때 코드를 실행하기 위해 사용되는 인터페이스!
✔️Persistence Context에 저장할 엔티티 클래스 생성
import javax.persistence.*;
@Getter
@Setter
@NoArgsConstructor
@Entity
public class Member {
@Id
@GeneratedValue
private Long memberId;
private String email;
public Member(String email) {
this.email = email;
}
}
- @Entity + @Id 애너테이션으로 JPA에서 해당 클래스를 엔티티 클래스로 인식하게 한다.
- @GeneratedValue 애너테이션은 식별 생성 전략을 지정할 때 사용된다.
멤버변수에 추가할 경우 DB 테이블에서 기본키(Primary Key)가 되는 식별자를 자동으로 설정해준다.
☑️ JpaConfig 수정 - 1
@Configuration
public class JpaConfig {
private EntityManager em;
@Bean
public CommandLineRunner testJpaRunner(EntityManagerFactory emFactory) {
this.em = emFactory.createEntityManager();
return args -> example01();
}
private void example01() {
Member member = new Member("aaaa@gmail.com");
em.persist(member);
Member resultMember = em.find(Member.class, 1L);
System.out.println("Id: " + resultMember.getMemberId() + ", email: " +
resultMember.getEmail());
}
}
EntityManager
- JPA Persistence Context를 관리하는 클래스이다.
- EntityManagerFactory의 createEntityManger() 메소드를 통해 EntityManager 객체를 생성한다.
- EntityManger 객체를 통해 JPA API 메소드를 호출한다(persist(), flush() 등).
em.persist(member);
- Persistence Context에 member 객체 정보 저장
find(Member.class, 1L);
- member 객체가 저장되었는지 확인
Member.class: 조회할 엔티티 클래스 타입
1L: 조회할 엔티티 클래스의 식별자 값(기본키)
em.persist(member)가 호출되면 1차 캐시에 member 객체가 저장되고, member 객체를 테이블에 INSERT하는 쿼리문이 쓰기지연 SQL 저장소에 등록된다.
- 로그에서 JPA(Hibernate) 가 내부적으로 테이블을 자동 생성하고 기본키를 할당해주는 것을 볼 수 있다.
- em.persist() 만으로는 실제 테이블에 회원 정보를 저장하지 않는다.
(로그에서도 INSERT 쿼리를 볼 수 없음)
☑️ JpaConfig 수정 - 2
Persistence Context와 DB 테이블 모두에 member 정보를 저장하도록 수정!
@Configuration
public class JpaConfig {
private EntityManager em;
private EntityTransaction tx;
@Bean
public CommandLineRunner testJpaRunner(EntityManagerFactory emFactory) {
this.em = emFactory.createEntityManager();
this.tx = em.getTransaction(); // 추가
return args -> example02();
}
private void example02() {
Member member = new Member("aaaa@gmail.com");
tx.begin(); // 추가
em.persist(member);
tx.commit(); // 추가
Member resultMember1 = em.find(Member.class, 1L);
System.out.println("Id: " + resultMember1.getMemberId() + ", email: " +
resultMember1.getEmail());
// 추가
Member resultMember2 = em.find(Member.class, 2L);
System.out.println(resultMember2 == null);
}
}
this.tx = em.getTransaction();
- EntityManager 객체를 통해 Transaction 객체를 얻는다.
- JPA에서는 Transaction 객체를 통해 DB 테이블에 데이터를 저장한다.
tx.begin();
- begin() 메소드가 호출되면 트랜잭션이 시작된다.
em.persist(member1);
- Persistence Context의 1차 캐시에 member 객체를 저장하고, 쓰기지연 SQL 저장소에는 INSERT 쿼리를 등록한다.
tx.commit();
- commit() 메소드가 호출되면, Persistence Context에 저장되어 있는 쿼리문이 실행되어 member 객체가 DB 테이블에 저장된다.
em.find(Member.clas, 1L);
- Persistence Context의 1차 캐시에 저장되어 있는 member 객체를 조회한다.
- 1차 캐시에 저장된 객체 정보를 불러오기 때문에 SELECT 쿼리는 따로 전송하지 않는다.
em.find(Member.clas, 2L);
- 현재 기본키가 1인 member 객체만 저장되어 있는 상태에서 기본키 2의 member 객체를 조회한다.
1. 1차 캐시에 기본키 2의 member 객체가 있는지 조회한다.
2. 현재 1차 캐시에는 존재하지 않기 때문에 INSERT 쿼리문을 통해 DB의 테이블에서 조회를 시도한다.
(로그에서 INSERT 쿼리문 확인 가능)
☑️ JpaConfig 수정 - 3
쓰기지연으로 Persistence Context와 DB 테이블에 엔티티 객체 일괄 저장하는 예제!
@Configuration
public class JpaConfig {
..
@Bean
public CommandLineRunner testJpaRunner(EntityManagerFactory emFactory) {
..
return args -> example03();
}
private void example03() {
Member member1 = new Member("aaaa@gmail.com");
Member member2 = new Member("bbbb@gmail.com"); // 추가
tx.begin();
em.persist(member1);
em.persist(member2); // 추가
tx.commit();
}
}
- tx.begin() 메소드로 트랜잭션 시작
- member1과 member2를 persist() 메소드로 Persistence Context(1차 캐시)에 저장
- tx.commit() 메소드 실행 전까지는 위의 그림과 같은 상태 유지 => DB 테이블에는 저장 x
- tx.commit() 메소드가 실행되면 쓰기지연 SQL 저장소에 있던 INSERT 쿼리 2개(?)가 모두 실행
=> DB 테이블에 데이터 저장(실행된 쿼리문은 저장소에서 제거됨)
로그에서 INSERT 쿼리문이 두 개가 실행되는 것을 확인할 수 있다.
☑️ JpaConfig 수정 - 4
DB 테이블에 저장된 데이터 업데이트 하는 예제!
@Configuration
public class JpaConfig {
..
@Bean
public CommandLineRunner testJpaRunner(EntityManagerFactory emFactory) {
..
return args -> example04();
}
private void example04() {
tx.begin();
em.persist(new Member("aaaa@gmail.com"));
tx.commit();
Member member2 = em.find(Member.class, 1L);
tx.begin();
member2.setEmail("bbbb@gmail.com");
tx.commit();
}
}
- tx.begin() 메소드로 트랜잭션 시작
- Member 클래스의 객체를 persist() 메소드로 Persistence Context(1차 캐시)에 저장
- tx.commit() 메소드로 쓰기지연 SQL 저장소에 등록된 INSERT 쿼리 실행
- em.find(Member.class, 1L)로 1차 캐시에서 member 정보 조회
(1차 캐시에 존재하기 때문에 DB 테이블에 쿼리문 전송 x) - setter 메소드로 이메일 정보 변경
⭐update() 메소드가 따로 존재하는게 아니라 그냥 setter 메소드로 불러온 객체의 값만 변경하면 됨!⭐
UPDATE 쿼리 실행 과정
- Persistence Context에 엔티티 정보가 저장될 경우, 저장된 시점의 상태의 스냅샷을 생성한다.
- Setter 메소드로 값을 변경한 후 commit() 메소드를 실행하면, 생성해둔 스냅샷과 현재상태를 비교 후 변경된 값이 있으면 쓰기지연 저장소에 UPDATE 쿼리를 등록한다.
- UPDATE 쿼리를 실행한다.
☑️ JpaConfig 수정 - 5
1차 캐시의 엔티티 정보와 테이블에 있는 엔티티 삭제하는 예제!
@Configuration
public class JpaConfig {
..
@Bean
public CommandLineRunner testJpaRunner(EntityManagerFactory emFactory) {
..
return args -> example05();
}
private void example05() {
tx.begin();
em.persist(new Member("aaaa@gmail.com"));
tx.commit();
Member member2 = em.find(Member.class, 1L);
tx.begin();
em.remove(member2); // 변경
tx.commit();
}
}
- tx.begin() 메소드로 트랜잭션 시작
- Member 클래스의 객체를 persist() 메소드로 Persistence Context(1차 캐시)에 저장
- tx.commit() 메소드로 쓰기지연 SQL 저장소에 등록된 INSERT 쿼리 실행
- em.find(Member.class, 1L)로 1차 캐시에서 삭제하기 위한 member 정보 조회
(1차 캐시에 존재하기 때문에 DB 테이블에 쿼리문 전송 x) - em.remove(member2)를 통해 1차 캐시에 있는 엔티티 제거 요청 & 쓰기지연 저장소에 DELETE 쿼리 등록
- tx.commit() 메소드 실행 시 1차 캐시의 엔티티 제거 & DELETE 쿼리 실행
-----------------------------------------------------------------------<2023.05.02>-----------------------------------------------------------------------
참고
https://suhwan.dev/2019/02/24/jpa-vs-hibernate-vs-spring-data-jpa/
JPA, Hibernate, 그리고 Spring Data JPA의 차이점
개요 Spring 프레임워크는 어플리케이션을 개발할 때 필요한 수많은 강력하고 편리한 기능을 제공해준다. 하지만 많은 기술이 존재하는 만큼 Spring 프레임워크를 처음 사용하는 사람이 Spring 프레
suhwan.dev
JPA & Hibernate & Spring Data JPA
- JPA
- Java application에서 RDB를 사용하는 방식을 정의한 인터페이스
- 사용할 수 있는 기능들을 모아둔 라이브러리가 아닌 단순히 사용법만 정의해둔 것
- JPA를 정의한 jakarta.persistence 패키지를 살펴보면 대부분 interface, enum, annotation 등으로 구성됨
- Hibernate
- JPA를 구현한 구현체(Java 문법관점으로는 interface와 interface를 구현한 class와 같은 관계)
- JPA를 구현한 구현체로는 hibernate 외에도 여러가지가 있고, 원한다면 직접 구현해서 사용가능
- Spring Data JPA
- JPA를 추상화한 spring 모듈
- 'Repository' 라는 인터페이스를 통해 spring이 적합한 구현체를 만들어 bean으로 등록할 수 있게 함
'🌿With Spring > JPA' 카테고리의 다른 글
한 엔티티의 PK를 2개의 FK로 가지는 클래스 (0) | 2023.08.04 |
---|---|
JPA에서의 엔티티 매핑(Entity Mapping) (0) | 2022.12.05 |