코드 그라데이션
영속성 컨텍스트 본문
JPA에서 가장 중요한 2가지
• 객체와 관계형 데이터베이스 매핑하기 (Object Relational Mapping)
- 정적인 매핑 과정
• 영속성 컨텍스트
- 실제 동작
엔티티 매니저 팩토리와 엔티티 매니저
영속성 컨텍스트
• JPA를 이해하는데 가장 중요한 용어
• “엔티티를 영구 저장하는 환경”이라는 뜻
• EntityManager.persist(entity);
=> DB에 저장한다기 보단, 영속성 컨텍스트를 통해 엔티티를 영속화하는 개념
(즉 이거는 DB에 저장이 아닌 영속성 컨텍스트에 저장한다.)
• 영속성 컨텍스트는 논리적인 개념
• 눈에 보이지 않는다.
• 엔티티 매니저를 통해서 영속성 컨텍스트에 접근
J2SE 환경 : 엔티티 매니저와 영속성 컨텍스트가 1:1
- 엔티티 매니저를 생성하면 영속성 컨텍스트가 있다고 생각하면 속 편함.
J2EE 환경, 스프링 프레임워크 같은 컨테이너 환경 : 엔티티 매니저와 영속성 컨텍스트가 N : 1
엔티티의 생명주기
비영속
- 세팅만 해놓고 아직 영속성 컨텍스트에 아무것도 넣지 않은 상태
영속
- 아직 DB에 저장한 것 아님. 커밋 이후 저장됨
준영속, 삭제
영속성 컨텍스트의 이점
==> 이렇게 생각하자 "데이터베이스와 애플리케이션 사이에 뭔가 중간 계층이 있다"
엔티티 조회, 1차 캐시
1차 캐시에서 조회
데이터베이스에서 조회
- 고객의 요청이 하나 들어와서 비즈니스가 끝나버리면 이 영속성 컨텍스트를 지운다.
- 1차 캐시도 다 날아간다.
- 이것은 데이터베이스의 한 트랜잭션 내에서만 동작한다.
실습
비영속 상태에서는 객체가 생성만 된 것이고,
영속에서 비로소 저장됨.
그러나, 영속 상태가 되었다고 해서(em.persist(member)) 반드시 데이터베이스에 저장되었다고 할 수는 없다
=> 즉, 커밋이 반드시 수행되어야 저장됨.
여기서 100L 조회해봤자 나오지도 않음.
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
//비영속
Member member = new Member();
member.setId(100L);
member.setName("Haerin");
//영속
System.out.println("BEFORE================");
em.persist(member);
System.out.println("AFTER================");
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
이렇게 해야 비로소 저장된 101L에 해당된 아이디와 이름이 조회됨.
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
//비영속
Member member = new Member();
// member.setId(100L);
member.setId(101L);
member.setName("Haerin");
//영속
System.out.println("BEFORE================");
// em.persist(member); //그런데 이 때 DB에 저장되는 건 아니다.
em.persist(member);
System.out.println("AFTER================");
Member findMember = em.find(Member.class, 101L);
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.name = " + findMember.getName());
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
두 번째 조회할 때는 DB에서 쿼리가 나가면 안 되고, 1차캐시에서 가져와야 하므로 쿼리가 한 번만 나가야한다.
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member findMember1 = em.find(Member.class, 101L);
Member findMember2 = em.find(Member.class, 101L);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
Hibernate:
select
member0_.id as id1_0_0_,
member0_.name as name2_0_0_
from
Member member0_
where
member0_.id=?
영속 엔티티의 동일성 보장
1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을
데이터베이스가 아닌 애플리케이션 차원에서 제공
=> 내가 자바 컬렉션에서 가져왔을 때 주소가 동일했던 것처럼, JPA가 == 비교를 보장해준다는 뜻. (1차 캐시 덕분)
실습
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member findMember1 = em.find(Member.class, 101L);
Member findMember2 = em.find(Member.class, 101L);
System.out.println("result = " + (findMember1 == findMember2));
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
13:56:09.084 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.EntityReferenceInitializerImpl - On call to EntityIdentifierReaderImpl#resolve, EntityKey was already known; should only happen on root returns with an optional identifier specified
13:56:09.094 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Resolving attributes for [inflearn.exjpa.Member#101]
13:56:09.095 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `name` : value = Haerin
13:56:09.095 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`name`) - enhanced for lazy-loading? - false
13:56:09.096 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Done materializing entity [inflearn.exjpa.Member#101]
13:56:09.099 [main] DEBUG org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader - Done entity load : inflearn.exjpa.Member#101
result = true
13:56:09.099 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - committing
13:56:09.099 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Processing flush-time cascades
13:56:09.104 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Dirty checking collections
13:56:09.107 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Flushed: 0 insertions, 0 updates, 0 deletions to 1 objects
13:56:09.107 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Flushed: 0 (re)creations, 0 updates, 0 removals to 0 collections
13:56:09.108 [main] DEBUG org.hibernate.internal.util.EntityPrinter - Listing entities:
13:56:09.108 [main] DEBUG org.hibernate.internal.util.EntityPrinter - inflearn.exjpa.Member{name=Haerin, id=101}
13:56:09.110 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl - Initiating JDBC connection release from afterTransaction
13:56:09.111 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl - Initiating JDBC connection release from afterTransaction
13:56:09.111 [main] DEBUG org.hibernate.internal.SessionFactoryImpl - HHH000031: Closing
13:56:09.112 [main] DEBUG org.hibernate.type.spi.TypeConfiguration$Scope - Un-scoping TypeConfiguration [org.hibernate.type.spi.TypeConfiguration$Scope@7a356a0d] from SessionFactory [org.hibernate.internal.SessionFactoryImpl@8692d67]
13:56:09.112 [main] DEBUG org.hibernate.service.internal.AbstractServiceRegistryImpl - Implicitly destroying ServiceRegistry on de-registration of all child ServiceRegistries
13:56:09.112 [main] INFO org.hibernate.orm.connections.pooling - HHH10001008: Cleaning up connection pool [jdbc:h2:tcp://localhost/~/test]
13:56:09.113 [main] DEBUG org.hibernate.boot.registry.internal.BootstrapServiceRegistryImpl - Implicitly destroying Boot-strap registry on de-registration of all child ServiceRegistries
종료 코드 0(으)로 완료된 프로세스
result = true가 출력됨.
엔티티 등록 : 트랜잭션을 지원하는 쓰기 지연
- 한 방에 모았다가, 커밋 시점에 보낸다.
그림으로 확인하면
옵션 중에 <property name = "hibernate.jdbc.batch-size" value="10" /> 이런 걸 쓰면,
이 value 크기만큼 모았다가 한 번에 쿼릴르 보내고 저장시킴
활용 잘 하면 옵션 하나로 기본적으로 성능을 먹고 들어갈 수 있다.
실습
일단 Member 클래스에 생성자 삽입
@Entity
@NoArgsConstructor
public class Member {
public Member(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Id
private Long id;
private String name;
//Getter, Setter …
}
그리고
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
// 롬복 의존성 추가 후 생성자 Member 클래스에 만들어놓고
Member member1 = new Member(150L, "A");
Member member2 = new Member(160L, "B");
em.persist(member1);
em.persist(member2); // 여기까지 영속성 컨텍스트에 쌓임
System.out.println("==========쿼리 나가는 거 구분선==========");
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
이러면
14:23:44.523 [main] DEBUG org.hibernate.loader.entity.plan.EntityLoader - Static select for entity inflearn.exjpa.Member [NONE]: select member0_.id as id1_0_0_, member0_.name as name2_0_0_ from Member member0_ where member0_.id=?
14:23:44.542 [main] DEBUG org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator - No schema actions specified
14:23:44.542 [main] DEBUG org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator - No actions specified; doing nothing
14:23:44.542 [main] DEBUG org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator - No JtaPlatform was specified, checking resolver
14:23:44.543 [main] DEBUG org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformResolverInitiator - No JtaPlatformResolver was specified, using default [org.hibernate.engine.transaction.jta.platform.internal.StandardJtaPlatformResolver]
14:23:44.546 [main] DEBUG org.hibernate.engine.transaction.jta.platform.internal.StandardJtaPlatformResolver - Could not resolve JtaPlatform, using default [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
14:23:44.546 [main] INFO org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
14:23:44.549 [main] DEBUG org.hibernate.service.internal.SessionFactoryServiceRegistryImpl - EventListenerRegistry access via ServiceRegistry is deprecated. Use `sessionFactory.getEventEngine().getListenerRegistry()` instead
14:23:44.551 [main] DEBUG org.hibernate.hql.internal.QueryTranslatorFactoryInitiator - QueryTranslatorFactory: org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory@3a7704c
14:23:44.554 [main] DEBUG org.hibernate.query.spi.NamedQueryRepository - Checking 0 named HQL queries
14:23:44.554 [main] DEBUG org.hibernate.query.spi.NamedQueryRepository - Checking 0 named SQL queries
14:23:44.555 [main] DEBUG org.hibernate.internal.SessionFactoryRegistry - Initializing SessionFactoryRegistry : org.hibernate.internal.SessionFactoryRegistry@54a3ab8f
14:23:44.556 [main] DEBUG org.hibernate.internal.SessionFactoryRegistry - Registering SessionFactory: 6673586b-09e0-44dd-8e9c-ade941cc5636 (<unnamed>)
14:23:44.556 [main] DEBUG org.hibernate.internal.SessionFactoryRegistry - Not binding SessionFactory to JNDI, no JNDI name configured
14:23:44.601 [main] DEBUG org.hibernate.stat.internal.StatisticsInitiator - Statistics initialized [enabled=false]
14:23:44.607 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false
14:23:44.607 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - begin
14:23:44.612 [main] DEBUG org.hibernate.event.internal.AbstractSaveEventListener - Generated identifier: 150, using strategy: org.hibernate.id.Assigned
14:23:44.630 [main] DEBUG org.hibernate.event.internal.AbstractSaveEventListener - Generated identifier: 160, using strategy: org.hibernate.id.Assigned
==========쿼리 나가는 거 구분선==========
14:23:44.631 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - committing
14:23:44.631 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Processing flush-time cascades
14:23:44.632 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Dirty checking collections
14:23:44.636 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Flushed: 2 insertions, 0 updates, 0 deletions to 2 objects
14:23:44.636 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Flushed: 0 (re)creations, 0 updates, 0 removals to 0 collections
14:23:44.637 [main] DEBUG org.hibernate.internal.util.EntityPrinter - Listing entities:
14:23:44.637 [main] DEBUG org.hibernate.internal.util.EntityPrinter - inflearn.exjpa.Member{name=A, id=150}
14:23:44.637 [main] DEBUG org.hibernate.internal.util.EntityPrinter - inflearn.exjpa.Member{name=B, id=160}
14:23:44.644 [main] DEBUG org.hibernate.SQL -
/* insert inflearn.exjpa.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
Hibernate:
/* insert inflearn.exjpa.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
14:23:44.650 [main] DEBUG org.hibernate.SQL -
/* insert inflearn.exjpa.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
Hibernate:
/* insert inflearn.exjpa.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
14:23:44.653 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl - Initiating JDBC connection release from afterTransaction
14:23:44.653 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl - Initiating JDBC connection release from afterTransaction
14:23:44.653 [main] DEBUG org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl - HHH000420: Closing un-released batch
14:23:44.654 [main] DEBUG org.hibernate.internal.SessionFactoryImpl - HHH000031: Closing
14:23:44.654 [main] DEBUG org.hibernate.type.spi.TypeConfiguration$Scope - Un-scoping TypeConfiguration [org.hibernate.type.spi.TypeConfiguration$Scope@238ad8c] from SessionFactory [org.hibernate.internal.SessionFactoryImpl@78f5c518]
14:23:44.655 [main] DEBUG org.hibernate.service.internal.AbstractServiceRegistryImpl - Implicitly destroying ServiceRegistry on de-registration of all child ServiceRegistries
14:23:44.655 [main] INFO org.hibernate.orm.connections.pooling - HHH10001008: Cleaning up connection pool [jdbc:h2:tcp://localhost/~/test]
14:23:44.657 [main] DEBUG org.hibernate.boot.registry.internal.BootstrapServiceRegistryImpl - Implicitly destroying Boot-strap registry on de-registration of all child ServiceRegistries
종료 코드 0(으)로 완료된 프로세스
엔티티 수정 : 변경 감지
변경 감지 (Dirty Checking)
- JPA는 값을 변경하면 트랜잭션이 커밋 되는 시점에 무조건 변경을 반영한다고 생각하면 편함
엔티티 삭제
실습.
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
// em.persist(member); 필요가 없어요.
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member = em.find(Member.class, 150L); // 현재 이름은 A
member.setName("ZZZ");
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
Hibernate:
/* update
inflearn.exjpa.Member */ update
Member
set
name=?
where
id=?
14:36:12.691 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl - Initiating JDBC connection release from afterTransaction
14:36:12.692 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl - Initiating JDBC connection release from afterTransaction
14:36:12.692 [main] DEBUG org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl - HHH000420: Closing un-released batch
14:36:12.692 [main] DEBUG org.hibernate.internal.SessionFactoryImpl - HHH000031: Closing
14:36:12.692 [main] DEBUG org.hibernate.type.spi.TypeConfiguration$Scope - Un-scoping TypeConfiguration [org.hibernate.type.spi.TypeConfiguration$Scope@60c16548] from SessionFactory [org.hibernate.internal.SessionFactoryImpl@78f5c518]
14:36:12.693 [main] DEBUG org.hibernate.service.internal.AbstractServiceRegistryImpl - Implicitly destroying ServiceRegistry on de-registration of all child ServiceRegistries
14:36:12.693 [main] INFO org.hibernate.orm.connections.pooling - HHH10001008: Cleaning up connection pool [jdbc:h2:tcp://localhost/~/test]
14:36:12.694 [main] DEBUG org.hibernate.boot.registry.internal.BootstrapServiceRegistryImpl - Implicitly destroying Boot-strap registry on de-registration of all child ServiceRegistries
종료 코드 0(으)로 완료된 프로세스
'Spring > JPA 공부' 카테고리의 다른 글
엔티티 매핑 1 - 객체와 테이블 매핑 (0) | 2023.08.15 |
---|---|
flush와 준영속 상태 (0) | 2023.08.14 |
JPA의 등장과 발전 (0) | 2023.08.12 |
SQL 중심적인 개발의 문제점 (0) | 2023.08.12 |
JPQL 간단한 소개 (0) | 2023.08.11 |