JPA는 자바의 인터페이스로 정의되어 있는 ORM기술의 표준 사양이다. 따라서, JPA를 구현한 구현체는 따로 존재한다.
JPA표준사양을 구현한 구현체가 하이버네이터이다.
이 외 EclipseLink, DataNucleus 등이 있지만 설명은 생략한다.
하이버네이터 ORM은 JPA에서 정의해둔 인터페이스를 구현한 구현체로써 JPA의 지원기능 이외에 자체적으로 사용할 수 있는 API도 지원하고 있다.
JPA는 서버의 데이터 액세스 계층 상단에 위치한다.
데이터를 저장, 조회 등 작업은 JPA를 거쳐 JPA의 구현체인 하이버네이터 ORM을 통해 JDBC API를 이용하여 데이터베이스에 접근한다.
Persistence란 영속성, 지속성이라는 뜻이다. 즉, 사라지지 않고 오래 지속되게 한다라는 의미를 가지고있다.
ORM은 객체와 데이터베이스 테이블을 매핑하여 엔티티 클래스 객체안에 있는 정보를 테이블에 저장하는 기술이다.
💡JPA에서의 영속성 컨텍스트란?
영속성 컨텍스트에 데이터베이스의 테이블과 매핑되는 엔티티 객체 정보를 보관하여 애플리케이션 내에서 오래 지속되도록 하는 것이다.
보관된 엔티티 정보는 데이터베이스 테이블에 데이터를 저장, 수정, 조회, 삭제하는데 사용된다.
영속성 컨텍스트에는 1차캐시영역과 쓰기 지연 SQL저장소 라는 영역이 있다.
API를 사용하여 영속성 컨텍스트에 엔티티(개체, 데이터)를 저장하면 1차캐시에 정보가 저장된다.
dependencies{
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // (1)
}
spring:
h2:
console:
enabled: true
path: /h2
datasource:
url: jdbc:h2:mem:test
jpa:
hibernate:
ddl-auto: create # (1) 스키마 자동 생성
show-sql: true # (2) SQL 쿼리 출력
@Entity, @Id
JPA에서 애너테이션이 붙은 해당 클래스를 엔티티 클래스로 인식한다.
@GeneratedValue
데이터베이스 테이블에서 애너테이션이 붙은 해당 변수를 기본키가 되는 식별자로 자동 설정해준다.
@Configuration
public class JpaBasicConfig {
private EntityManager em;
@Bean
public CommandLineRunner testJpaBasicRunner(EntityManagerFactory emFactory) { // (1)
this.em = emFactory.createEntityManager(); // (2)
return args -> {
example01();
};
}
5) EntityManagerFactory의 createEntityManager() 메서드를 이용해서 EntityManager 클래스의 객체를 얻을 수 있다.
6) em.persist(member) 메서드 호출을 통해 영속성 컨텍스트에 member객체의 정보들 저장.
em.persist(member);
em.persist(member)를 호출하면 1차 캐시에 member객체가 저장되고 이 member 객체는 쓰기 지연 SQL 저장소에 INSERT 쿼리 형태로 등록이 된다.
Member resultMember = em.find(Member.class, 1L);
// 첫 번째 파라미터 mamber.class = 조회 할 엔티티 클래스 타입
// 두 번째 파라미터 1L = 조회 할 엔티티 클래스의 식별자 값
this.tx = em.getTransaction();
1) EntityManager를 통해서 Transaction 객체를 얻은 후 이 객체를 기준으로 데이터베이스의 테이블에 데이터를 저장한다.
2) Transaction을 시작하기 위해서는 .begin()메서드를 먼저 호출해주어야 한다.
3) .persist()메서드를 통해서 객체를 영속성 컨텍스트에 저장한다.
em.persist(member);
즉, tx.commit(); 으로 트랜잭션을 커밋하여 데이터베이스에 변경 내용을 반영한다.
tx.commit();
5) em.find(Member.class, 1L)을 호출하면 .persist()메서드를 통해 영속성 컨텍스트에 저장했던 멤버 객체를 1차 캐시에서 조회한다. 1차 캐시에 member객체 정보가 있기 때문에 별도로 테이블에 SELECT쿼리를 전송하지 않는다.
6) em.find(Member.class, 2L)를 호출해서 식별자 값이 2L인 member 객체를 조회한다. 하지만 영속성 컨텍스트에는 식별자 값이 2L인 member객체는 존재하지 않는다 따라서 resultMember2 == null의 결과는 true가 된다.
7) 영속성 컨텍스트에서 식별자 값이 2L인 member객체가 존재하지 않기 때문에 테이블에 직접 SELECT쿼리를 전송한다.
⭐️ 중요하게 기억해야 할것!!
- em.persist()를 호출하면 영속성 컨텍스트의 1차 캐시에 엔티티 클래스의 객체가 저장되고, 쓰기 지연 SQL저장소에 INSERT쿼리가 등록된다.
- tx.commit() 을 하는 순간 쓰기 지연 SQL저장소에 등록된 INSERT쿼리가 실행되고, 실행된 INSERT쿼리는 쓰기 지연 SQL저장소에서 제거된다.
- em.find()를 호출하면 먼저 1차 캐시에서 해당객체가 있는지 조회한 뒤, 없으면 테이블에 SELECT쿼리를 전송해서 조회한다.
위의 그림은 tx.commit()메서드가 실행되기 직전의 영속성 컨텍스트 상태를 표현한 것이다. tx.commit()을 해야만 em.persist()를 통해 쓰기 지연 SQL저장소에 등록된 INSERT쿼리가 실행된다.
즉, tx.commit() 통해 쿼리를 실행시켜야 테이블에 데이터가 저장된다는 의미이다.
위의 그림은 tx.commit()후의 영속성 컨텍스트 상태이다. tx.commit()이 실행되면 쓰기 지연 SQL저장소에 등록된 INSERT쿼리가 모두 실행되고 실행된 커리는 제거된다. -> 테이블에 데이터 저장됨.
@Configuration
public class JpaBasicConfig {
private EntityManager em;
private EntityTransaction tx;
@Bean
public CommandLineRunner testJpaBasicRunner(EntityManagerFactory emFactory) {
this.em = emFactory.createEntityManager();
this.tx = em.getTransaction();
return args -> {
example04();
};
}
private void example04(){
tx.begin();
em.persist(new Member("hgd1@gmail.com"));
tx.commit();
tx.begin();
Member member1 = em.find(Member.class,1L);
member1.setEmail("hgd1@yahoo.co.kr");
tx.commit();
}
}
실행과정은 이렇다.
테이블에 저장된 member객체를 조회할 때는 테이블이 아닌 영속성 컨텍스트의 1차 캐시에서 조회를 한다.
영속성 컨텍스트의 1차 캐시에 이미 저장된 객체가 있기 때문에 영속성 컨텍스트에서 조회한다는 사실을 기억해야한다.
JPA API에 update()가 있을 것 같지만 setter()로 값을 변경하기만 하면 업데이트 로직은 .commit()이 실행될 때 쓰기 지연 SQL저장소에 등록된 UPDATE쿼리가 실행된다.
삭제는 위의 코드에서 setter()대신 remove()를 사용하면 된다.