JPA 표준 사양을 구현한 구현체는 바로 Hibernate ORM,Eclipse Link DataNucleus 등이 있다.
Persistence는 영속성,지속성이라는 뜻을 가지고 있다. 즉, 무언가를 금방 사라지지 않고 오래 지속되게 한다라는 것이 Persistence의 목적이다
영속성 컨텍스트를 그림으로 표현하면
그림과 같이 영속성 컨텍스트에는 1차 캐시라는 영역과 쓰기 지연 SQL저장소라는 영역이 있다.
JPA API중에서 엔티티 정보를 영속성 컨텍스트에 저장하는 API를 사용하면 영속성 컨텍스트의 1차 캐시에 엔티티정보가 저장된다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // (1)
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
[코드] 의존성 주입
spring:
h2:
console:
enabled: true
path: /h2
datasource:
url: jdbc:h2:mem:test
jpa:
hibernate:
ddl-auto: create # (1) 스키마 자동 생성
show-sql: true # (2) SQL 쿼리 출력
[코드] JPA 설정 추가
@Configuration
public class JpaBasicConfig {
private EntityManager em;
private EntityTransaction tx;
// (2)
@Bean
public CommandLineRunner testJpaBasicRunner(EntityManagerFactory emFactory) {
this.em = emFactory.createEntityManager();
this.tx = em.getTransaction();
return args -> {
// (3) 이 곳에 학습할 코드를 타이핑합니다.
};
}
[코드] 샘플 코드 작성
@Configuration
애너테이션을 추가하면 Srping에서 Bean에서 bean 검색 대상인 Configuration 클래스로 간주해서 (2)와 같이 @Bean 애너테이션이 추가된 메서들르 검색한 후, 해당 메서드에서 리턴하는 객체를 Spring Bean으로 추가해 준다.
(3)과 같이 CommandLineRunner
객체를 람다 표현식으로 정의해 주면 애플리케이션 부트스트랩 과정이 완료된 후에 이 람다 표현식에 정의한 코드를 실행해 준다.
EntityManagerFactory
애플리케이션 로딩 시점에 DB당 딱 하나만 생성되어야 한다.
EntityManager
엔티티를 관리하는 역할을 수행하는 클래스
트랜잭션을 수행시키기 위해 EntityManagerFactory로부터 DB 커넥션을 하나 생성받는 개념
필요할 때마다 EntityManagerFactory로부터 발급받고, 사용 후에 close()를 호출해 반납한다.
쓰레드 간에 절대 공유하면 안된다.
EntityTransaction
JPA의 데이터를 변경하는 모든 작업은 반드시 트랜잭션 안에서 수행되어야 한다.
완료되면 commit을 해주고, 문제가 생기면 Rollback을 해줘야 한다.
@Getter
@Setter
@NoArgsConstructor
@Entity // (1)
public class Member {
@Id // (2)
@GeneratedValue // (3)
private Long memberId;
private String email;
public Member(String email) {
this.email = email;
}
[코드] JPA 동작 확인을 위한 Member 엔티티 클래스
@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();
};
}
private void example01() {
Member member = new Member("hgd@gmail.com");
// (3)
em.persist(member);
// (4)
Member resultMember = em.find(Member.class, 1L);
System.out.println("Id: " + resultMember.getMemberId() + ", email: " +
resultMember.getEmail());
}
}
[코드] Member엔티티를 영속성 컨텍스트에 저장하는 코드
em.persist(member)
를 호출하면 그림과 같이 1차캐시에 member 객체가 저장되고 이 member 객체는 쓰기 지연 SQL 저장소에 INSERT쿼리 형태로 등록이 된다.
위 코드를 실행하면, 아래와 같은 출력 값을 얻을 수 있다.
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))
2023-04-22 14:53:33.171 INFO 49271 --- [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2023-04-22 14:53:33.174 INFO 49271 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2023-04-22 14:53:33.214 WARN 49271 --- [ main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2023-04-22 14:53:33.347 INFO 49271 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8083 (http) with context path ''
2023-04-22 14:53:33.353 INFO 49271 --- [ main] c.c.Section3Week2JpaApplication : Started Section3Week2JpaApplication in 1.298 seconds (JVM running for 1.539)
Hibernate: call next value for hibernate_sequence
Id: 1, email: hgd@gmail.com
테이블에 이미 저장되어 있는 데이터를 JPA를 이용해서 어떻게 업데이트 할 수 있는지에 대해 실습해보려 한다.
@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")); // (1)
tx.commit(); // (2)
tx.begin();
Member member1 = em.find(Member.class, 1L); // (3)
member1.setEmail("hgd1@yahoo.co.kr"); // (4)
tx.commit(); // (5)
System.out.println(member1.getEmail());
}
}
위 코드를 실행시키면 아래와 같은 결과가 출력된다.
Update 쿼리가 실행이 되는 과정
@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 -> {
example05();
};
}
private void example05() {
tx.begin();
em.persist(new Member("hgd1@gmail.com")); // (1)
tx.commit(); //(2)
tx.begin();
Member member = em.find(Member.class, 1L); // (3)
em.remove(member); // (4)
Member member2 = em.find(Member.class,1L);
System.out.printf("member is not exist : %b",member2==null);
}
}
해당 코드를 실행하게 되면 아래와 같은 결과를 출력받는다.
flush() :
tx.commit() 메서드가 호출되면 JPA 내부적으로 em.flush() 메서드가 호출되어 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영
생명주기
엔티티는 4가지 상태가 존재한다.
@Entity 어노테이션을 갖는 엔티티 인스턴스를 막 생성했을 때는 영속성 컨텍스트에서 관리하지 않는다.
EntityManger의 persist메소드를 사용하여 영속 상태로 변경할 수 있다.
em.persist(someEntity);
EntityManager
를 통해 데이터를 영속성 컨텍스트에 저장했다.
JPA는 일반적으로 id 필드가 존재하지 않으면 예외를 뱉어내는데, 영속 상태의 엔티티를 고나리하기 위해서다.
id로 데이터를 관리하기 때문에 꼭 필요하다
em.find(key)
를 호출하면 영속성 컨텍스트에 캐시된 데이터를 먼저 찾는다.REPEATABLE READ
등급의 트랜잭션 격리 수준을 활용한다.//1.
em.detach(someEntity);
//2.
em.close();
//3.
em.clear();
em.remove(someEntity);
em.flush()
를 활용하면 직접 플러시할 수 있다.
@FlushModeType.AUTO (default)
@FlushModelType.COMMIT
영속성 컨텍스트를 종료하려면 EntityManager
의 close 메소드를 호출한다.
em.close();
준영속 상태의 데이터는 병합 기능을 사용하여 다시 영속 상태로 돌릴 수 있다.
someEntity entity = em.find(key);
em.detach(entity);
em.merge(entity);