Spring JPA 영속성 컨텍스트

justindevcode·2022년 10월 3일

스프링 JPA

목록 보기
3/3
post-thumbnail

Spring JPA 영속성 컨텍스트


intro

spring 데이터 수정부분을 배우며 어찌나 강조하시던지 JPA 영속성 컨텍스트에대해 알아보았다.

사실 이전 Django ORM을 사용하면서도 update에 문제가 많이 생겼는데 이번에 공부해보면서 DB와 Java사이에 데이터가 이동한다는게 보통일이 아닌거같다. 그렇지만 그만큼 너무너무 중요하기에 이 복잡한걸 JPA같은거로 아주 잘 만들어뒀는데

그럼에도 문제가 생길때가 있는것이다. 이 복잡한걸 너무 잘 만들어놔서 JPA함수 몇개 외워쓰고 하는식으로는 언젠가 문제가 터지는거다. Django때 처럼...


영속성 컨텍스트란?

영속성(persistency)이란 데이터를 영구적으로 저장하는 것을 의미한다.

따라서 영속성 컨텍스트란 직역하자면 데이터를 영구적으로 저장하는 환경 정도로 해석될 수 있다.

기본적으로 JPA는 객체지향 언어인 JAVA와 Database 사이의 패러다임 불일치를 해결하기 위해서 도입된 규약

EntityManger객체.persist(Entity객체) 를 실행하면 영속성 컨텍스트가 Entity 를 관리하게 된다.

EntityManager

EntityManager는 영속성 컨텍스트 내에서 Entity들을 관리하고 있다.
EntityManager는 JPA에서 제공하는 interface로 spring bean으로 등록되어 있어 Autowired로 사용할 수 있다.

@Autowired
private EntityManager entityManager;

영속성 컨텍스트의 생명주기

비영속(new/transient)

  • 영속성 컨텍스트와 전혀 관계가 없는 상태이다.
  • 엔티티 객체를 생성하였지만 아직 영속성 컨텍스트에 저장하지 않은 상태를 의미한다.
//객체만 생성한 비영속상태 
User user = new User();

영속(managed)

  • 영속성 컨텍스트에 저장된 상태
  • 엔티티가 영속성 컨텍스트에 의해 관리된다.
  • 영속 상태가 되었다고 바로 DB에 값이 저장되지 않고 트렌젝션의 커밋 시점에 영속성 컨텍스트에 있는 정보들을 DB에 쿼리로 날리게 된다.
@Autowired
private EntityManager entityManager;
// Class내에 Autowired로 EntityManager추가

    //객체만 생성한 비영속상태 
    User user = new User();
    
    // 객체를 저장한 영속상태
    entityManager.persist(user);

준영속(detached)

  • 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 엔티티를 준영속 상태로 만드려면 entityManager.detach()를 호출한다.
// 영속 -> 준영속
// user엔티티를 영속성 컨텍스트에서 분리하면 준영속 상태가 된다.
entityManager.detach(user);
// 영속성 콘텍스트를 비우면 관리되고 있던 엔티티들은 준영속 상태가 된다. (대기 상태에 있는 변경 데이터들도 삭제)
entityManager.clear();
// 영속성 콘텍스트를 종료해도 관리되던 엔티티들은 준영속 상태가 된다.
entityManager.close();
    
// 준영속 -> 영속 
// detach를 하여 준영속상태에 빠진 entity를 merge를 하면 다시 영속 상태가 된다.
entityManager.merge(user); 

준영속 상태의 특징

  • 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩을 포함한 영속성 컨텍스트가 제공하는 어떠한 기능도 동작하지 않는다.
  • 식별자 값 을 가지고 있다.

삭제(removed):

  • 영속성 컨텍스트와 DB에서 해당 엔티티를 삭제하여 삭제된 상태이다.
// user엔티티를 영속성 컨텍스트와 DB에서 삭제
entityManager.remove(user);

과정 살펴보기

영속성 컨텍스트는 데이터를 불러오면 1차 캐시에 저장해둡니다.

우리가 트랜잭션 안에서 불러온 객체를 수정한다면 모든 변화는 영속성 컨텍스트에 반영됩니다.

그리고 트랜잭션이 마무리 될때 1차 캐시에 있는 정보와 DB에 있는 정보를 비교해서 필요한 쿼리를 모두 날려서 DB에 반영시켜줍니다.


실무에서 주의할점

아래 예시는 수정이 필요해서 Book book = new Book();에서 '새로만든'객체에 id를 DB에 존재하는 애를 넣어서 내가 만든것이다.

@PostMapping("items/{itemId}/edit")
    public String updateItem(@PathVariable String itemId, @ModelAttribute("form") BookForm form) {
        Book book = new Book();

        book.setId(form.getId());
        book.setName(form.getName());
        book.setPrice(form.getPrice());
        book.setStockQuantity(form.getStockQuantity());
        book.setAuthor(form.getAuthor());
        book.setIsbn(form.getIsbn());

        itemService.saveItem(book);
        return "redirect:/items";

    }

업데이트부분을 보면 Book book = new Book();새로생성한 객체이지만 book.setId(form.getId());내가 있던 id를 넣은것 이런 애를 준영속 엔티티라고 한다.
그래서 이 준영속성 엔티티를 수정하기위해서는 변경감지와 머지 두가지 방식이 있지만 변경감지를 권장한다.

변경감지

//Service
@Transactional
public void updateItem(Long itemId, Book param) {
    Item findItem = itemRepository.findOne(itemId);
    findItem.setPrice(param.getPrice());
    findItem.setName(param.getName());
    findItme.setStockQuantity(param.getStockQuantity());
    .
    .
    .
    return findItem;
}

위의 코드는 결국 새로생성한 Book book = new Book();은 DTO처럼 사용하여 진짜로 가장 처음 생성된 객체를 DB에서 끄집어내서 거기에다가 수정해주는것이다.
그러면 DB에서 끄집어낸 객체는 영속성엔티티이기에 저렇게 대입으로 수정을해줬다? 알아서 변경감지해서 DB에 반영시켜줌 그냥 영속성엔티티를 사용하는 방식인거다.

병합 사용

@Transactional
void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티
 Item mergeItem = em.merge(item);
}

지금 예제코드가 이렇게 되어있는데 em.merge(item);로 되어있는 이게 내부에서 위의 변경감지 코드 저거를 jpa가 알아서 해주는것이라고 보면된다.
결국 내가 넘겨준 item얘는 영속성이 되는게아니고 mergeItem얘가 영속성이 되는것

이때 merge도 자동으로 해주는데 문제는 변경 감지 기능을 사용하면 원하는 속성만 선택해서 변경할 수 있지만, 병합을 사용하면 모든 속성이 변경된다. 병합시 값이 없으면 null 로 업데이트 할 위험도 있다. (병합은 모든 필드를 교체한다.)

우리는 가격은 한번결정되면 다시는 못바꾸게하자 라고 해서 수정칸에는 가격칸을 없앴는데 병합을 사용하면 값이 null로 바뀐다.


영속성 컨텍스트의 이점

1. 1차 캐시

영속성 컨텍스트는 내부적으로 1차 캐시가 존재한다.

1차 캐시는 Map<Key(primary key), Value(Entity)> 형태로 저장한다.

이렇게 되면, DB에 직접 조회를 요청하지 않아도 캐시에서 바로 조회가 가능하다는 이점이 있다.

존재하지 않는다면, DB에서 조회하여 캐시에 저장하고, 저장되어 있는 동안에는 빠른 조회가 가능하다.

2. 동일성 보장

영속성 컨텍스트는 엔티티의 동일성을 보장한다.

3. 쓰기 지연

transaction이 시작되는 시점부터 끝나는 시점까지 SQL을 모아 두고,

transaction이 종료되는 시점에 transaction.commit()이 호출됨과 동시에 모아둔 쿼리를 모두 보낸다.

4. 변경 감지

위 예시에서 보듯 별도의 update 쿼리를 사용하지 않더라도 자동으로 commit시 db에 있는 데이터와 값 비교를 통해 변경을 감지한다. 이를 Dirty Checking이라고 한다.


Reference

profile
("Hello World!");

0개의 댓글