2023. 2. 23. 18:11ㆍJPA
◎ JPA(Java Persistance API)
-> 자바 진영에서 사용하는 ORM 기술의 *표준 사양
* 표준 사양은 자바의 인터페이스로 사양이 정의되어 있어 JPA라는 표준 사양을 구현한 구현체는 따로 있음을 의미한다.
◎ Hibernate ORM
-> JPA에서 정의한 인터페이스를 구현한 구현체로 JPA에서 지원하는 기능 외에 hibernate자체적으로 사용할 수 있는 API도 지원한다.
◎ 데이터 액세스 계층과 JPA
-> jpa는 데이터 액세스 계층 상단에 위치한다.
-> 데이터 저장, 조회 등 데이터 작업은 JPA를 거쳐 JPA의 구현체인 Hibernate ORM을 통해 진행되고, Hibernate ORM은 내부적으로 JDBC API를 사용해서 데이터베이스에 접근한다.
◎ 영속성 컨텍스트
-> JPA의 P는 Persistance(영속, 지속성)이란 뜻이다. 어떤 데이터가 들어와서 오래 지속됨을 의미한다.
-> ORM은 객체와 데이터베이스 테이블의 매핑을 통해 엔티티 클래스 내부에 포함된 정보를 테이블에 저장하는 기술이다. jpa에서 테이블과 매핑되는 엔티티 객체 정보를 영속성 컨텍스트라는 곳에 보관하여 애플리케이션 내에서 지속적으로 관리한다.
-> 영속성 컨텍스트에는 1차 캐시, 쓰기 지연 SQL 저장소로 이루어져 있다.
-> 엔티티 정보를 영속성 컨텍스트에 저장하면 1차 캐시에 해당 엔티티가 저장된다.
-> 예시 1
@Getter
@Setter
@NoArgsConstructor
@Entity // 엔티티 클래스로 지정
public class Member { // 엔티티 클래스
@Id
@GeneratedValue
private Long memberId;
private String email;
public Member(String email) {
this.email = email;
}
}
@Configuration
public class JpaBasicConfig {
private EntityManager em; // 영속성 컨텍스트는 EntityManager에 의해 관리된다.
private EntityTransaction tx;
@Bean
public CommandLineRunner testJpaBasicRunner(EntityManagerFactory emFactory) {
this.em = emFactory.createEntityManager();
this.tx = em.getTransaction();
return args -> {
example01();
};
}
private void example01(){
Member member = new Member("hong@email.com");
em.persist(member); // 영속성 컨텍스트에 member 객체 정보가 저장된다.
// find의 첫 번째 파라미터는 조회할 엔티티의 클래스 타입, 두 번째 파라미터는 조회할 엔티티 클래스의 식별자 값
Member resultMember = em.find(Member.class, 1L);
System.out.println("id : " + resultMember.getMemberId() + ", email : " + resultMember.getEmail());
}
}
-> 결과
Hibernate: drop table if exists member CASCADE
Hibernate: drop sequence if exists hibernate_sequence
Hibernate: create sequence hibernate_sequence start with 1 increment by 1
Hibernate: create table member (member_id bigint not null, email varchar(255), primary key (member_id))
...
Hibernate: call next value for hibernate_sequence
id : 1, email : hong@email.com
-> 결과를 보면 jpa가 내부적으로 테이블을 자동으로 생성하고 테이블의 기본키도 할당한다.
-> em.persist(member)를 호출할 경우 영속성 컨텍스트에 member객체가 저장되지만 실제 테이블에 회원 정보를 저장하는 것은 아니다.
-> 예시2
@Configuration
public class JpaBasicConfig {
private EntityManager em; // 영속성 컨텍스트는 EntityManager에 의해 관리된다.
private EntityTransaction tx; // JPA에서는 이 Transaction 객체를 기준으로 데이터베이스의 테이블에 데이터를 저장
@Bean
public CommandLineRunner testJpaBasicRunner(EntityManagerFactory emFactory) {
this.em = emFactory.createEntityManager();
this.tx = em.getTransaction();
return args -> {
example02();
};
}
private void example01(){
Member member = new Member("hong@email.com");
em.persist(member); // 영속성 컨텍스트에 member 객체 정보가 저장된다.
// find의 첫 번째 파라미터는 조회할 엔티티의 클래스 타입, 두 번째 파라미터는 조회할 엔티티 클래스의 식별자 값
Member resultMember = em.find(Member.class, 1L);
System.out.println("id : " + resultMember.getMemberId() + ", email : " + resultMember.getEmail());
}
private void example02(){
tx.begin(); // Transaction 시작
Member member = new Member("hsh@email.com");
em.persist(member); // member를 영속성 건텍스트에 저장
tx.commit(); // commit()호출 시점에 영속성 컨텍스트에 저장된 member 객체를 데이터베이스의 테이블에 저장
// 영속성 컨텍스트에 있는 member 객체를 1차 캐시에서 조회, 1차 캐시에 member 객체 있으면 별도로 테이블에 SELECT 쿼리 전송하지 않음
Member resultMember = em.find(Member.class, 1L);
System.out.println("id : " + resultMember.getMemberId() + ", email : " + resultMember.getEmail());
Member resultMember2 = em.find(Member.class, 2L);
System.out.println(resultMember2 == null); // 식별자가 2L인 member객체는 없기 때문에 true
}
}
-> 결과
Hibernate: drop table if exists member CASCADE
Hibernate: drop sequence if exists hibernate_sequence
Hibernate: create sequence hibernate_sequence start with 1 increment by 1
Hibernate: create table member (member_id bigint not null, email varchar(255), primary key (member_id))
...
Hibernate: call next value for hibernate_sequence
Hibernate: insert into member (email, member_id) values (?, ?)
id : 1, email : hsh@email.com
Hibernate: select member0_.member_id as member_i1_0_0_, member0_.email as email2_0_0_ from member member0_ where member0_.member_id=?
true
- member 객체가 영속성 컨텍스트에 저장되고, 이후 em.find(Member.class, 1L)을 호출하면 영속성 컨텍스트에 저장된 member객체를 1차 캐시에서 조회한다.
- 식별자 값이 2L인 member 객체를 조회하는데 해당 객체는 영속성 컨텍스트에 없기 때문에 영속성 컨텍스트에서 찾지 않고 select 쿼리를 테이블에 직접 전송한다.
※ em.persist() 호출 시 영속성 컨텍스트의 1차 캐시에 엔티티 클래스의 객체가 저장되고, 쓰기 지연 저장소에 INSERT 쿼리가 등록된다.
※ tx.commit()을 하면 쓰기 지연 SQL 저장소에 등록된 INSERT 쿼리가 실행되고, 실행된 INSERT 쿼리는 쓰기 지연 SQL 저장소에서 사라진다.
※ em.find() 호출 시 해당 객체가 1차 캐시에 있는지 찾아보고 없으면 테이블에 SELECT 쿼리를 전송하여 조회한다.
-> 예시3
private void example03(){
tx.begin();
Member member1 = new Member("hong@yahoo.co.kr");
Member member2 = new Member("hong@yaho.co.kr");
em.persist(member1);
em.persist(member2);
tx.commit();
}
- em.persist(member1), em.persist(member2)로 member1,member2 객체가 영속성 컨텍스트에 저장되고, 쓰기 지연 SQL 저장소에 저장된 INSERT 쿼리는 실행되지 않다가 tx.commit()이 호출되면 INSERT쿼리가 모두 실행되고 실행된 쿼리는 쓰기 지연 SQL 저장소에서 사라진다.
-> 결과
Hibernate: call next value for hibernate_sequence
Hibernate: call next value for hibernate_sequence
Hibernate: insert into member (email, member_id) values (?, ?)
Hibernate: insert into member (email, member_id) values (?, ?)
-> 예시4(영속성 컨텍스트와 테이블에 엔티티 업데이트)
private void example04(){
tx.begin();
em.persist(new Member("hong@yahoo.co.kr"));
tx.commit(); // 쓰기 지연 SQL 저장소 내 INSERT 쿼리 실행, member 객체는 테이블에 저장됨
tx.begin();
Member member1 = em.find(Member.class, 1L); // 테이블에 저장된 member 객체를 영속성 컨텍스트 1차 캐시에서 조회
member1.setEmail("hong@naver.com");// 이메일 변경
tx.commit(); // 쓰기 지연 SQL 저장소 내 UPDATE 쿼리 실행
}
-> 결과
Hibernate: drop table if exists member CASCADE
Hibernate: drop sequence if exists hibernate_sequence
Hibernate: create sequence hibernate_sequence start with 1 increment by 1
Hibernate: create table member (member_id bigint not null, email varchar(255), primary key (member_id))
...
Hibernate: call next value for hibernate_sequence
Hibernate: insert into member (email, member_id) values (?, ?)
Hibernate: update member set email=? where member_id=?
- 엔티티 값을 setter메서드로 변경 후 tx.commit()을 하면 변경된 엔티티와 이전에 상태의 엔티티의 스냅샷을 비교하여 변경된 값이 있으면 쓰기 지연 SQL 저장소에 UPDATE 쿼리를 등록하고 UPDATE 쿼리를 실행한다.
-> 예시5(영속성 컨텍스트와 테이블의 엔티티 삭제)
private void example05(){
tx.begin();
em.persist(new Member("hong@yahoo.co.kr")); // Member 클래스 객체를 영속성 컨텍스트 1차 캐시에 저장
tx.commit();
tx.begin();
Member member = em.find(Member.class, 1L); // 위에서 영속성 컨텍스트에 저장된 Member 클래스 객체를 영속성 컨텍스트 1차 캐식에서 조회
em.remove(member); // 영속성 컨텍스트 1차 캐시에 있는 엔티티 삭제 요청
tx.commit(); // 영속성 컨텍스트 1차 캐시에 있는 엔티티 객체 지우고 쓰기 지연 SQL 저장소에 등록된 DELETE 쿼리 실행
}
-> 결과
Hibernate: drop table if exists member CASCADE
Hibernate: drop sequence if exists hibernate_sequence
Hibernate: create sequence hibernate_sequence start with 1 increment by 1
Hibernate: create table member (member_id bigint not null, email varchar(255), primary key (member_id))
...
Hibernate: call next value for hibernate_sequence
Hibernate: insert into member (email, member_id) values (?, ?)
Hibernate: delete from member where member_id=?
※ tx.commit()메서드가 호출되면 JPA 내부적으로 em.flush()메서드가 호출되어 영속성 컨텍스트의 변경사항을 데이터베이스의 테이블에 반영한다.