본문 바로가기
JPA

JPA 내부구조 알아보기

by bloodFinger 2020. 9. 14.

엔티티의 생명주기 

비영속(new/reansient)

- 영속성 컨텍스트와 전혀 관계가 없는 상태

 

영속(managed)

- 영속성 컨텍스트에 저장된 상태

 

준영속(detached)

- 영속성 컨텍스트에 저장되었다가 분리된 상태

 

삭제(removed)

- 삭제된 상태

 


 

영속

//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원");

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

//객체를 저장한 상태(영속)
em.persist(member);

비영속

//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

 

준영속 , 삭제

//회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);


//객체를 삭제한 상태(삭제)
em.remove(member);

 


 

영속성 컨텍스트가 존재하는 이유는 무엇일까?

 

영속성 컨텍스트의 이점

  • 1차 캐시
  • 동일성(identity) 보장
  • 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)
  • 변경 감지(Dirty Checking)
  • 지연 로딩(Lazy Loading)

1차 캐시

em.persist(member);

- 1차 캐시에 저장

Member findMember = em.find(Member.class , "member");

- 1차 캐시 확인 있다면 DB 조회를 하지않고 캐시에서 데이터를 가져온다.

Member findMember2 = em.find(Member.class , "member2");

- 1차 캐시에 없다면 DB에 가서 조회한다. 그 후에 데이터를 1차캐시에 저장 후 반환

 

 

트랜잭션을 지원하는 쓰기 지연(transactional write-behind)

em.persist(memberA);

1. insert SQL(memberA 데이터) 생성해서 쓰기 지연 SQL 저장소에 쿼리를 저장해놓는다.

2. 1차 캐시에 저장한다.

 

em.persist(memberB);

1. insert SQL(memberB 데이터) 생성해서 쓰기 지연 SQL 저장소에 쿼리를 저장해놓는다.

2. 1차 캐시에 저장한다.

 

transaction.commit();

1. flush() - 쓰기 지연 SQL 저장소의 내용을 insert SQL (memberA , memberB 데이터) DB에 전달

2. DB commit();

 

 

영속 엔티티의 동일성 보장

Member a = em.find(Member.class , "member1");
Member b = em.find(Member.class , "member1");

System.out.println(a == b); 
/*
  동일성 비교 true 왜냐 내부에 1차캐시가 존재하기 때문에
  같은 래퍼런스 반환
*/

1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공

 

 

변경 감지(Dirty Checking)

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();

//영속 엔티티 조회
Member memberA = em.find(Member.class , "memberA");

//영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);
//em.update(member) 라는 코드가 있어야 하지 않을까?

trasaction.commit();

 

영속성 컨텍스트에 스냅샷을 떠두고 비교해서 바뀐부분을 update 쿼리로 만들어 DB에 전달한다.

 

 

왜 update 코드가 없어도 변경을 가능하게 만들어놨을까? 그건 자바의 사상때문이다.

public class Test2 {
	private String name;
	private int age;
	
	public Test2(String name ,int age){
		this.name = name;
		this.age = age;
	}

	public static void main(String[] args) {	
		Test2 t1 = new Test2("a" , 1);
		Test2 t2 = new Test2("b" , 2);
		Test2 t3 = new Test2("c" , 3);
		
		List<Test2> list = new ArrayList<Test2>();
		list.add(t1);
		list.add(t2);
		list.add(t3);
		
		for (Test2 test2 : list) 
			System.out.println(test2.name + "/ " + test2.age);
		
		//값 변경
		t2.age=123;
		
		for (Test2 test2 : list) 
			System.out.println(test2.name + "/ " + test2.age);
	}
}

t2.age 값을 변경하고 다시 list를 조회해보니 자동으로 변경되었다.

 


플러쉬

- 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영

 

  • 변경 감지
  • 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
  • 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송(등록 , 수정 , 삭제 쿼리)

 

영속성 컨텍스트를 플러시하는 방법 

  • em.flush() - 직접 호출
  • 트랜잭션 Commit - 자동 호출
  • JPQL 쿼리 실행 - 자동 호출

 

플러시는 

영속성 컨텍스트를 비우지 않는다.

영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화가 목적이다.

트랜잭션이라는 작업 단위가 중요하다 - > 커밋 직전에만 동기화 하면 된다.

 

 


준영속 상태로 만드는 방법

  • em.detach(entity) -> 특정 엔티티만 준영속 상태로 전환
  • em.clear() -> 영속성 컨텍스트를 완전히 초기화
  • em.close() -> 영속성 컨텍스트를 종료

 

 

Member를 조회할 때 Team도 함께 조회해야 할까?

public class Member {

	@Id
    @GeneratedValue
    private Long id;
    
    @Column(name = "USERNAME")
    private String name;
    
    @ManyToOne(Fetch = FetchType.LAZY)
    @JoinColumn(name = "TEAM_ID")
    private Team team;
    ..

}

LAZY로 걸면 Member를 조회하면 member만 조회가 된다.

 

 

 어떤게 이렇식으로 진행되는가?

 

지연 로딩 LAZY을 사용해서 프록시로 조회

Member member = em.find(Member.class , 1L);

 

Team team = member.getTeam();

team.getName(); // 실제 team을 사용하는 시점에 초기화(DB 조회)

 

 

  • 가급적이면 지연 로딩을 사용해라
  • 즉시로딩을 적용하면 예상하지 못한 SQL이 발생한다.
  • 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.

 

지연로딩을 사용하려면 영속성 컨텍스트에서 관리되어야 한다.

그렇지 않다면 LazyInitiallizationException 의 예외가 발생한다.

'JPA' 카테고리의 다른 글

Spring Data JPA & QueryDSL  (0) 2021.01.07
JPA 사용시 주의사항 정리  (0) 2020.08.22
연관관계 매핑  (0) 2020.05.25
JPA 매핑 어노테이션  (0) 2020.05.24
이제는 Mybatis 에서 JPA로 넘어가자  (0) 2020.04.17