영속성 컨텍스트 (Persistence context)
JPA에서 객체의 값을 수정하는 작업을 진행하려고 Query를 만들어 작동을 시켰는데 에러가 발생했다...??
분명 작동시 SQL 쿼리문이 정상적으로 나오고 있는걸 콘솔에서 확인 했는데 정작 그 코드가 적용이 되지 않는것이다
그래서 알아보니 영속성 컨텍스트에 의해서 내가 작성한 Update 쿼리 메서드는 영속성 컨텍스트에 저장되어 있는 캐시값을 변경시키지 않고 DB에 저장되어있는 정보에 대해서만 수정/변경 사항을 적용시키기 때문에 내가 영속성 컨텍스트에 저장해놓은 데이터의 값과 실제 데이터 값이 일치하지 않으면서 오류가 발생하게 된것이다.
그래서 찾은 방법이 @Modifying 어노테이션을 이용하여 영속성 컨텍스트의 1차캐시를 삭제 시켜버리는 방법이다.
@Modifying(clearAutomatically = true)
해당 속성은 @Modifying이 적용된 쿼리 메서드를 실행한 후, 영속성 컨텍스트를 clear 할 것인지를 지정하는 속성으로,
속성의 default는 false로 되어있다 , 그래서 true로 변경해주면 해당 메서드 (UPDATE)가 실행된 후 현재 영속성 컨텍스트 (1차 캐시)에 저장되어 있던 기존의 Entity의 값을 지워버리고 새롭게 Select해 오기 때문에 이전 정보와 현재 정보가 다르게 적용되는 오류가 발생하지 않게 할 수 있다..!!
일단 저 방법으로 해당 오류는 잡았지만 JPA를 사용하면서 항상 듣게되는 "영속성 컨텍스트"에 대해서조금 더 알아보고 가는게 좋을것 같아서 영속성 컨텍스트에 대한 글을 한번 써본다!!
영속성 컨텍스트란 ??
Entity를 영구 저장시켜주는 환경 이라는 뜻으로. 애플리케이션과 데이터베이스 사이에서 객체를 보관하는 가상의 데이터 베이스 같은 역할을 한다.
엔티티 매니저가 Entity를 저장하거나 조회하면 매니저는 영속성 컨텍스트에 Entity를 보관하고 관리한다.
엔티티 매니저가 persist()메서드를 사용하면 엔티티를 영속성 컨텍스트에 저장한다.
em.persist(member); // member Entity를 엔티티매니저가 저장
하나의 엔티티 매니저가 하나의 영속성 컨텍스트를 생성 및 접근 할 수도 있고,
여러 엔티티 매니저가 하나의 영속성 컨텍스트를 공유 할 수도 있다.
엔티티 매니저( Entity Manager )
- 특정 작업을 위해 데이터베이스에 엑세스하는 역할을 담당한다.
- 엔티티를 DB에 등록/수정/삭제/조회 (CRUD)하는 역할이며, 엔티티와 관련된 일을 처리하는 엔티티 관리자.
- 엔티티 매니저는 데이터베이스 커넥션과 밀접한 관계가 있으므로 스레드간에 공유하거나 재사용하면 안된다. 사용이 끝난 엔티티 매니저는 반드시 종료해야 한다.
엔티티 매니저 팩토리
설정 정보를 읽어와 DB커넥션 풀도 생성하기 때문에 엔티티 매니저 팩토리를 생성하는 비용이 크다. 따라서 엔티티 매니저 팩토리는 애플리케이션 전체에서 딱 한번만 생성하고 공유해서 사용해야 한다.
Entity의 생명 주기
- 비영속(new/transient) = 영속성 컨텍스트와 전혀 관계가 없는 상태
- 엔티티 객체를 생성했지만 아직 영속성 컨텍스트에 저장하지 않은 상태를 비영속(new/transient)라 한다.
- Member member = new Member();
- 엔티티 객체를 생성했지만 아직 영속성 컨텍스트에 저장하지 않은 상태를 비영속(new/transient)라 한다.
- 영속 (managed) = 영속성 컨텍스트에 저장된 상태
- 엔티티 매니저를 통해서 엔티티를 영속성 컨텍스트에 저장한 상태를 말하며 영속성 컨텍스트에 의해 관리된다는 뜻이다.
- em.persist(member);
- 엔티티 매니저를 통해서 엔티티를 영속성 컨텍스트에 저장한 상태를 말하며 영속성 컨텍스트에 의해 관리된다는 뜻이다.
- 준영속 (datached) = 영속성 컨텍스트에 저장되었다가 분리된 상태
- 영속성 컨텍스트가 관리하던 영속 상태의 엔티티 더이상 관리하지 않으면 준영속 상태가 된다. 특정 엔티티를 준영속 상태로 만드려면 em.datach()를 호출하면 된다.
- 준영속 상태의 특징
- 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩을 포함한 영속성 컨텍스트가 제공하는 어떠한 기능도 동작하지 않는다.
- 식별자 값을 가지고 있다.
// 엔티티를 영속성 컨텍스트에서 분리해 준영속 상태로 만든다.
em.detach(member);
// 영속성 콘텍스트를 비워도 관리되던 엔티티는 준영속 상태가 된다.
em.claer();
// 영속성 콘텍스트를 종료해도 관리되던 엔티티는 준영속 상태가 된다.
em.close();
- 삭제 (removed) = 삭제된 상태
- 엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제한다.
em.remove(member);
실제로는 EntityManager를 직접 작성하는 경우는 거의 없고, 주로 Repository Interface를 구현하여 많이 사용 한다.
Repository Interface의 장점은 다음과 같다.
- EntityManager를 직접 작성하지 않아도 데이터베이스에 Entity의 CRUD 처리됨
- @Repository annotation을 추가하지 않아도 Bean 으로 자동 등록됨
여기서 말하는 Repository interface는 Spring Data JPA이다. JPA 와 Spring Data JPA 는 완전히 같은 개념이 아니다.
Spring Data JPA를 사용하지 않는다면 EntityManager에서 제공하는 API를 사용하면 된다.
JPA ( Java Persistence API ) 예제
@Repository
@RequiredArgsConstructor
public class OrderRepository {
private final EntityManager em;
public void save(Order order){
em.persist(order);
}
public Order findOne(Long id){
return em.find(Order.class, id);
}
public List<Order> findAll() {
return em.createQuery("select o from Order o", Order.class)
.getResultList();
}
public List<Order> findAllWithItem() {
return em.createQuery(
"select distinct o from Order o" +
" join fetch o.member m" +
" join fetch o.delivery d" +
" join fetch o.orderItems oi" +
" join fetch oi.item i", Order.class)
.getResultList();
}
}
위 OrderRepository는 Spring Data JPA가 제공하는 JpaRepository를 상속하지 않고 @Repository만 추가했다. EntityManager가 제공하는 API를 활용해 적절한 API를 만들면 된다.
이렇게 JPA(Java Persistence API)에서 제공하는 EntityManager로 데이터베이스에 접근할 수 있다. 다시말해 JPA는 자바 진영에서 ORM 기술 표준으로 Application 과 JDBC 사이에서 동작한다.
- ORM (Object Relational Mapper) : 직접 SQL을 작성하지 않고도 객체지향 방식으로 DB에 접근
- JDBC (Java Database Connectivity) : Java에서 DB에 접속할 수 있도록 하는 자바 API
우리가 주로 사용하는 Hibernate는 JPA의 대표적인 프레임워크이다.