객체와 관계형 데이터베이스 매핑 (Object Relational Mapping)
영속성 컨텍스트
JPA 사용하기 위해선 먼저 엔티티 매니저 팩토리와 엔티티 매니저에 대해서 이해해야한다.
웹 애플리케이션이 실행될 때 엔티티 매니저가 같이 생성되고 (싱글톤?), 클라이언트의 요청이 올 때마다 엔티티 매니저 팩토리를 통해서 엔티티 매니저를 생성한다.
생성된 엔티티 매너저는 내부적으로 DB 커넥션을 사용해서 DB 접근한다.
EntityManger.persist(enitity);
영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
영속성 컨텍스트에 관리되는 상태
em.persist(entity)
영속성 컨텍스트에 저장되었다가 분리된 상태
삭제된 상태
//객체를 생성한 상태 (비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
//객체를 생성한 상태 (비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
//JPA 영속성 컨텍스트로의 접근은 엔티티 매니저를 통해서 한다
EntityManger em = emf.createEntityManger();
//JPA의 모든 데이터 변경은 트랜잭션 안에서 일어난다
em.getTransaction.begin();
//객체를 저장한 상태 (영속)
em.persist(member);
영속 상태가 된다고해서 바로 DB에 쿼리가 날라가는 것이 아니다!
DB에는 이후에 커밋해야 저장된다!
//회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);
//객체를 삭제한 상태(삭제), db 삭제를 요청하는 것
em.remove(member);
//엔티티를 생성한 상태 (비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
//엔티티를 영속
em.persist(member);
영속성 컨텍스트는 내부에 1차 캐시라는 것을 들고있다
1차캐시
Key) @Id : DB PK로 매핑한 필드
Value) @Entity : JPA가 관리하는 엔티티 객체
이렇게 하면 무슨 장점이 있지?
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
//1차 캐시에 저장됨
em.persist(member);
//1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");
JPA는 엔티티 조회해올 때 1차캐시부터 뒤진다.
1차 캐시에서 해당되는 키값을 가진 엔티티가 있다면, 1차 캐시에서 조회해온다.
`Member findMember2 = em.find(Member.class, "member2);
1차 캐시에 해당되는 키값을 가진 엔티티가 없다면, 그때 DB에 조회 쿼리 날려서 가져온 후 1차캐시에 엔티티 저장한다.
1차캐시에 저장된 엔티티를 반환한다.
(캐싱 전략의 read through 같은 느낌?)
사실 이게 그렇게 큰 성능상 이점은 아님
엔티티 매니저는 데이터베이스 트랜잭션 단위로 생성하고, 트랜잭션이 끝날때 같이 종료시켜버림
클라이언트 요청이 들어와서 하나의 비즈니스 로직이 끝나버리면 영속성 컨텍스트를 지움 (1차 캐시도 날라감) - 따라서 그 찰나의 순간에만 이득이 있음, 여러명의 고객이 사용하는 그런 캐시가 아니다.
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); //동일성 비교 true
1차 캐시로 반복가능한 읽기(Repeatable Read) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공
무슨 말이냐면 마치 자바 컬렉션에서 조회하듯이, JPA가 영속 엔티티의 동일성을 보장해준다는 것
가능한 이유가 바로 1차캐시가 있기 때문이다
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야함
transaction.begin(); // 트랜잭션 시작
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 DB에 보내지 않음
//JPA가 쿼리 쭉쭉 쌓고 있는다
//커밋하는 순간 DB에 INSERT SQL을 날린다
transaction.commit(); //트랜잭션 커밋
em.persist(memberA);
로 memberA를 1차캐시에 넣는다그럼 쿼리 도대체 언제 날라감??
트랜잭션을 커밋하면, 커밋하는 시점에 쓰기 지연 SQL 저장소에 있던 쿼리들이 flush되면서 날라간다.
그리고 실제 DB 트랜잭션이 커밋된다.
굳이 왜 이렇게 하는 것일까? 그냥 바로 바로 날려도 되지 않나?
여기서 바로 버퍼링이라는 기능이 나온다!
만약 매번 엔티티 영속화할 때마다 DB에 쿼리를 날린다고 해보자.
그렇게되면 아예 최적화할 수 있는 여지 자체가 없어진다.
사실 데이터베이스에 아무리 데이터 많이 집어넣어도 커밋 안하면 말짱 꽝이다. (DB 반영이 안되니까) 따라서 커밋하기 직전에만 INSERT 치면 된다.
커밋하기 직전에 위의 예시에서 보면 쓰기지연 SQL저장소에 쿼리들이 쌓여있다.
이 쿼리들을 한번에 날릴 수 있다. HOW? JDBC Batch사용
그냥 Batch사용하면 굉장히 코드가 복잡하지만, Hibernate같은 경우 batch_size 옵션 한줄로 적용할 수 있다!!!
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) 이런 코드가 있어야하지 않나?
transaction.commit(); //트랜잭션 커밋
어떻게 저런 것이 가능한걸까?
//삭제 대상 엔티티 조회
Member memberA = em.find(Member.class, "memberA");
em.remove(memberA); //엔티티 삭제
영속성 컨텍스트의 변경내용을 데이터베이스에 반영하는 것
영속성 컨텍스트의 쿼리들을 DB에 날려주는 것이라고 보면됨
데이터베이스 트랜잭션이 커밋되면 자동으로 flush가 발생한다고 보면 된다.
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
//중간에 JPQL실행
query = em.createQuery("select m from Member m", Member.class);
List<Member> members = query.getResultList();
memberA, B, C 1차캐시에만 저장하고 바로 JPQL로 DB에서 모든 member 객체 조회해오면 ABC값이 조회될까?
당연히 안됨! DB에 반영안됐는데 될 수가 없음
그렇기 때문에 JPQL이 호출되면 실행전에 먼저 현재 1차캐시 상태를 DB에 반영하는 flush가 자동으로 호출되는 것이다!!
플러시는!!
해당 게시글은 인프런 김영한님의 <자바 ORM 표준 JPA 프로그래밍 - 기본편>을 듣고 정리한 내용입니다.