티스토리 뷰

SPRING/JPA

영속성 컨텍스트?

란텔 2017. 9. 1. 15:39

엔티티를 지속적으로 저장하고 있는 속성을 영속성 컨텍스트 라고 말할 수 있다.


JPA에서는 기본적으로 EntityManagerFactory객체를 이용해서 EntityManager객체를 생성하고,

EntityManager객체의 메서드를 이용해서 조회, 저장, 변경, 삭제의 작업을 수행한다.


영속성 컨텍스트는 눈으로 확인 할 수는 없지만, EntityManager를 생성할 때 같이 만들어지고 EntityManager를 닫을 때 사라진다. 


Entity에는 다음과 같은 상태가 존재한다.


▶ 비영속

@Entity로 지정한 객체를 생성했을 뿐 EntityManager를 사용하고 있지 않은 상태이다.


▶ 영속

영속성 컨텍스트가 엔티티를 관리할 수 있는 상태를 '영속' 상태라 한다.

EntityManager의 persist()메서드, find()메서드, JPQL쿼리 등을 사용하면 해당 엔티티 객체는 '영속' 상태가 된다.


▶ 준영속

'영속' 상태이던 엔티티 객체가 EntityManager의 detach()메서드나 close()메서드를 호출하면 '준영속' 상태가 된다.


▶ 삭제

EntityManager의 remove() 메서드를 사용하면 '영속성 컨텍스트'와 '데이터베이스'에서 해당 엔티티 객체를 삭제하게 된다.



영속성 컨텍스트는 식별자 값을 필수로 요구하며, 엔티티 객체에서는 @Id 어노테이션이 데이터베이스 에서는 기본키가 반드시 있어야 한다.





영속성 컨텍스트의 특징


1차 캐시

- 영속성 컨텍스트는 내부에 캐시를 가지고 있으며 '영속'상태가 되는 모든 엔티티는 이곳에 저장된다. 이 캐시를 1차 캐시라 한다.

@Autowired private EntityManagerFactory emf; private EntityManager em; public void testInsertMember(Member member) throws SQLException{ em = emf.createEntityManager(); EntityTransaction et = em.getTransaction(); et.begin(); em.persist(member); et.commit(); em.close(); }

em.persist(member)를 실행하게 되면 바로 데이터베이스로의 작업이 수행되지 않고 1차 캐시에 Entity가 저장된다.


EntityManager em.....; Member m = em.find(Member.class, member.getId());

기본적으로 JPA는 위와 같은 방법으로 EntityManager의 find()메서드를 사용해서 조회를 한다. 조회 작업시  바로 데이터베이스를 조회하지 않고 먼저1차 캐시에서 Entity를 찾고 만약 1차 캐시에 일치하는 Entity가 없을 경우 데이터베이스를 조회한다.




영속상태 엔티티의 동일성 보장

EntityManager em...; Member m = em.find(Member.class, member.getId()); Member m1 = em.find(Member.class, member.getId()); System.out.println(m.hashCode()); System.out.println(m1.hashCode()); if(m == m1){ System.out.println("두 인스턴스는 같습니다."); }

=====결과========

742131994 <------------------ 두 인스턴스가 가리키는 주소의 해시코드가 같다.

742131994 <------------------ 두 인스턴스가 가리키는 주소의 해시코드가 같다.

두 인스턴스는 같습니다. <---------- 그러므로 두 인스턴스는 같다.

================

위 코드를 보면 find()메서드를 이용하여 같은 식별자를 가지는 조회작업을 두번 실행하고 있다. 그리고 두 인스턴스를 비교하고 있다

.

일반적인 statement객체로의 원시 쿼리나 Mybatis같은 기술로 위와 같은 작업을 거치면 Member엔티티의 변수 m과 m1은 같은 식별자값을 조회하는 것이라도 서로다른 인스턴스를 가질 것이다.


그러나 JPA '영속성 컨텍스트'는 첫번째 find()메서드 에서는 데이터베이스에서 식별자와 일치하는 값을 찾아오고 이 값이 '영속'상태가 되기 때문에 두번 째 find()조회 작업은 1차 캐시에서 일치하는 식별 Entity가 있으므로 이를 가져오고 데이터베이스를 거치지 않는다.

이처럼 '영속성 컨텍스트'는 Entity의 동일성을 보장한다.


물론 동일성을 일부러 지키지 않게 할 수도 있다. 영속상태의 Entity를 '준영속' 상태로 만들면 된다.

EntityManager객체에는 detach()라는 메서드가 있다. 이 메서드를 사용하면 해당 Entity객체는 1차 캐시에서 사라지고 준영속 상태가 된다.

Member m = em.find(Member.class, member.getId()); em.detach(m); Member m1 = em.find(Member.class, member.getId());

위와 같이 detach()메서드를 추가하면 Entity객체 변수 m은 '준영속' 상태가 되고 1차 캐시에서 사라지기 때문에 이후에 조회하는 m1과는 서로다른 인스턴스를 가질 수 밖에 없다.




쓰기 지연

em = emf.createEntityManager();
EntityTransaction et = em.getTransaction();
et.begin();
em.persist(member);
em.persist(member1);
em.persist(member2);
et.commit();
        
em.close();

EntityManager는 트랜잭션을 commit하기 전까지는 데이터베이스 작업을 하지 않는다. 대신 EntityManger내부의 쿼리 저장소에 실행할 쿼리를 쌓아 놓는다. 

그리고 EntityManager의 commit()메서드를 호출할 때 쿼리 저장소에 있는 쿼리들을 실행한다.




엔티티 수정시 변경감지

EntityManager em; EntityTransaction et = em.getTransaction(); et.begin(); Member m = em.find(Member.class, member.getId()); m.setAddress("해남 땅끝마을222222222"); et.commit(); em.close();

위 코드처럼 Entity를 수정할 때는 단순히 데이터를 조회해서 해당 Entity클래스의 setter메서드를 이용하여 변경해주면 된다.

이런식으로 영속상태 Entity의 데이터를 변경만하면 데이터베이스에도 반영하게 된다. 이러한 기능을 '변경감지'라고 한다.


JPA가 변경감지로 인해 데이터베이스 작업을 할 때 변경된 필드에 대해서만 적용을 할 것 같지만 실상은 그렇지 않다.

JPA의 업데이트 기본전략은 모든 필드를 업데이트 하는 것이다.

위 예제처럼 Entity객체인 Member변수 m의 setAddress메서드를 호출하여 주소 변경만 하고 있지만, 실상은 아래와 같이 모든 필드를 업데이트한다.  

Hibernate: update member set address=?, age=?, birth=?, email=?, name=?, nick=?, pass=? where id=?


상황에 따라 다르지만 테이블에 칼럼이 20~30개 정도 되면 JPA기본 전략인 모든 필드에 대한 업데이트를 하는 것보다 동적으로 변경된 값만 업데이트 해주는 전략을 사용해야 성능상 이점이 있다.


동적 업데이트를 사용하려면 Entity객체 클래스에 다음과 같은 DynamicUpdate어노테이션을 설정해야한다.

@Entity @org.hibernate.annotations.DynamicUpdate @Table(name="member") public class Member { .................... }

위와 같이 DynamicUpdate어노테이션을 Entity클래스에 설정하면 아래와 같이 변경된 필드에 대해서만 동적으로 SQL을 생성하여 데이터베이스에 보낸다.

Hibernate: update member set address=? where id=?




⑤ 엔티티 삭제

Member m = em.find(Member.class, member.getId()); em.remove(m);

EntityManager의 find메서드를 이용하여 삭제할 Entity객체를 가져온다. 그 후 remove메서드를 호출하면서 삭제할 Entity객체를 넘겨주면 해당 객체는 '영속성 컨텍스트'와 '데이터베이스'에서 삭제된다.

물론 코드에 적지 않았지만 트랜잭션을 커밋해야 최종적으로 flush(데이터베이스에 반영)가 된다.






플러시(flsush)란?

flush란 영속성 컨텍스트의 변경 내용(수정 및 삭제)을 데이터베이스에 적용하는 것


영속성 컨텍스트를 flush하는 방법은 3가지이다.

- EntityManager객체의 flush메서드를 직접호출하는 방법

     테스트 같은 경우에 사용할 뿐. 거의 사용하지 않는다.

- EntityManager의 getTransaction메서드 호출 후 EntityTransaction객체의 commit메서드를 호출하는 방법

     JPA는 commit메서드를 호출할 때 먼저 flush를 수행한다.

- JPQL 쿼리 실행 시 자동 호출




플러시(flush)의 옵션

플러시의 옵션에는 두가지가 있다.

EntityManager에 플러시의 옵션을 직접 설정하려면 javax.persistence.FlushModeType을 사용하면 된다.


FlushModeType.AUTO : 커밋이나 쿼리를 실행할 때 flush (기본값)

FlushModeType.COMMIT : 커밋할 때만 flush

EntityManager em; em.setFlushMode(javax.persistence.FlushModeType.COMMIT);

플러시모드를 별도로 설정하지 않으면 기본적으로 AUTO로 동작한다.

'SPRING > JPA' 카테고리의 다른 글

스프링 JPA 사용 설정 (Spring Legacy Project)  (0) 2017.08.29
Comments
최근에 올라온 글
최근에 달린 댓글
TAG
more
Total
Today
Yesterday