본문 바로가기
  • 시 쓰는 개발자
1일 1개념정리 (24년 8월~)/Spring

1일1개 (59) - 영속성

by poetDeveloper 2024. 10. 13.
반응형

1일 1개념정리 24.08.09.금 ~ 

 

큰 결정에 큰 동기가 따르지 않을 때도 있다. 하지만 큰 결심이 따라야 이뤄낼 수 있다.

무조건 무조건 1일 1개의 개념 정리하기 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!


#59. 영속성

Spring을 공부하다보면 JPA에서 영속성이라는 것을 마주치는데, 굉장히 어려워보인다 ... 매번 피하기만 하다가 한번 정리해보려고 한다.

 

영속성

일단 전체적인 맥락을 살펴보자. Spring에서 말하는 "영속성"은 애플리케이션의 데이터가 메모리에만 일시저장 되는 게 아니고, DB같은 외부 저장소에도 저장해서 애플리케이션을 종료해도 데이터가 유지되는 성질을 의미한다. 그래서 영속성 기능을 이용하려면 기본적으로 외부 DB(mysql 등)와 연결되어 있어야한다.

 

이를 위해 JPA를 사용하는데, JPA는 객체를 DB에 영구 저장하거나, DB에서 객체로 불러오는 과정을 쉽게 만들어준다. 이때 나오는 개념이 바로 "영속성 컨텍스트(Persistence Context)"이다. 영속성 컨텍스트는 말 그대로 "영구적으로 저장되는 환경"을 의미한다. 이때의 객체들은 메모리에서 관리하고, 엔티티 매니저(EntityManager)를 활용해 접근한다. 객체가 메모리에 있는 동안 변경된 내용을 DB에 자동으로 동기화한다. 다만 이는 메모리상의 동작으로, 실제로 DB에 반영되는 시점은 *플러시(Flush)가 발생할 때이다.

 

* Flush란 ?

플러시란, 영속성 컨텍스트에서 관리하는 모든 변경 사항을 DB에 동기화하는 작업이다. 영속성 컨텍스트에 저장된 객체들은 메모리에서만 관리되다가, 트랜잭션이 끝나거나 명시적으로 em.flush()를 호출할 때 비로소 DB에 반영된다.

 

영속성 컨텍스트의 장점

영속성 컨텍스트, 즉 영구적으로 저장되는 환경을 활용하면 무엇이 좋을까 ?? 크게 3가지를 이야기한다. 1차 캐시로 인한 엔티티 동일성 보장, 쓰기 지연, 변경 감지이다.

  • 1차 캐시 & 엔티티 동일성 보장 : 영속성 컨텍스트 내부에는 "1차 캐시"가 존재한다. 이는 DB에 바로 접근하지 않고, 우선 메모리에서 객체를 조회하여 성능을 향상시킨다. 예를 들어, 같은 트랜잭션 내에서 동일한 객체를 여러 번 조회하더라도 DB가 아닌 1차 캐시에서 조회하므로 성능상 이점이 있다. 이런 1차캐시 덕분에 항상 동일한 객체를 반환할 수 있어서 엔티티의 동일성을 보장하는 역할을 하기도 한다.
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); // true
  • 쓰기 지연 : JPA는 트랜잭션이 끝날 때까지는 SQL 쿼리를 즉시 실행하지 않고, 내부 저장소에 쿼리를 저장했다가 나중에 한번에 실행한다. 이를 통해 성능을 최적화할 수 있다.
em.persist(memberA);
em.persist(memberB);
// 아직 insert 쿼리 실행 X
em.getTransaction().commit(); // insert 쿼리 한 번에 실행
  • 변경 감지 : 영속 상태의 엔티티가 변경되면, JPA는 이를 감지하고 자동으로 DB에 UPDATE 쿼리를 실행한다.
Member member = em.find(Member.class, "member1");
member.setName("newName");
// 트랜잭션 커밋 시점에 자동으로 UPDATE 쿼리 발생
em.getTransaction().commit();

 

Entity 생명주기

엔티티 생명주기는 JPA에서 엔티티가 영속성 컨텍스트와 상호작용하는 과정을 단계별로 나타낸 것이다. 즉 엔티티 객체가 생성되고 삭제될 때까지의 과정을 여러 상태로 구분한 것이다. 이는 크게 4단계로 구분한다. 비영속-영속-준영속-삭제 이렇게 4단계로 구분하는데, 이를 단계별로 알아보자.

 

1. 비영속 상태 (New/Transient)

객체가 생성되었지만 아직 영속성 컨텍스트에 저장되지 않은 상태다. 이 상태에서는 DB와 전혀 연결되어 있지 않다.

Member member = new Member(); // 비영속 상태

 

2. 영속 상태 (Managed)

객체가 영속성 컨텍스트에 저장된 상태이다. 이때 JPA가 객체의 상태를 관리하며, 1차 캐시에 저장되어 필요할 때 DB에 반영된다.

em.persist(member); // 영속 상태

 

3. 준영속 상태 (Detached)

객체가 더 이상 영속성 컨텍스트에서 관리되지 않는 상태다. 객체는 여전히 존재하지만, DB와의 연결이 끊어졌다.

em.detach(member); // 준영속 상태

 

4. 삭제 상태 (Removed)

객체가 영속성 컨텍스트에서 제거되고 DB에서도 삭제된 상태다.

em.remove(member); // 삭제 상태

 

흐름을 정리해보면 이렇다.

// 비영속 상태
Member member = new Member();
member.setId(1L);
member.setName("John");

// 영속 상태
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(member); // 영속 상태로 전환됨
em.getTransaction().commit();

// 준영속 상태
em.detach(member); // 영속성 컨텍스트에서 분리됨

// 삭제 상태
em.remove(member); // 엔티티와 DB에서 삭제됨

 

Spring Data JPA에서 말하는 영속성

우리가 JPA를 쓴다 하면 보통 Spring Data JPA를 사용하게 될 것이므로, 여기서는 영속성을 어떻게 관리하고 있는지 알아보자.

 

Spring Data JPA는 DB작업에 대한 추상화된 레이어를 제공한다. 그래서 일일이 EntityManager를 다루지 않아도 된다. 우리는 jpa의 기능들을 활용하기만 하면 되는데, save(), find(), delete() 등의 메소드로 영속성을 관리할 수 있다.

 

1. save() : 새로운 엔티티를 영속성 컨텍스트에 저장하거나, 기존 엔티티가 변경된 경우 이를 감지하여 DB에 저장한다. 이 는 내부적으로 JPA의 persist() 또는 merge()를 호출한다.

 

2. findById() : 엔티티를 1차 캐시에서 먼저 검색하고, 없으면 DB에서 조회한다. 영속성 컨텍스트가 관리하는 엔티티는 동일성을 보장한다. (앞서 설명함)

 

3. delete() : 엔티티를 영속성 컨텍스트에서 제거하고, DB에서도 삭제하는 기능을 수행한다.

 

그래서 우리가 보통 보는 아래와 같은 코드가 되는 것이다.

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    // 새로운 사용자 저장 (영속성 컨텍스트에 저장)
    @Transactional
    public User saveUser(String name) {
        User user = new User();
        user.setName(name);
        return userRepository.save(user);  // 내부적으로 JPA의 persist() 호출
    }

    // 사용자 조회 - 1차 캐시 또는 DB에서 조회하게 됨.
    public User findUser(Long id) {
        return userRepository.findById(id).orElse(null);
    }

    // 사용자 삭제 - 영속성 컨텍스트 및 DB에서 삭제
    @Transactional
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
}
반응형