백엔드/JPA

[ JPA ] 6-3. Entity 생명주기

Reference. 한 번에 끝내는 Java/Spring 웹 개발 마스터 초격차 패키지 Online

이전 글

1. 비영속 상태

  • 영속성 컨텍스트가 해당 엔티티 객체를 관리하지 않는 상태
  • java에서 new 객체를 의미 EntityManager가 사용하는 상태 X
User의 비영속 상태
@Service
public class UserService {
    @Transactional
    public void put(){
        User user = new User();
        user.setName("newUSer");
        user.setEmail("newUser@test.com");
    }
}

2. 영속 상태

  • 엔티티가 영속성 컨텍스트 관리(manage)하에 있는 상태
  • Spring Data JPA에서 userRepository.save(user)와 같이 EntityManager를 사용하는 상태
    • JpaRepository.save()에는 EntityManager.persist()를 호출
    @Service
    public class UserService {
        @Autowired
        private EntityManager entityManager;
        @Autowired
        private UserRepository userRepository;
    
        @Transactional
        public void put(){
            User user = new User();
            user.setName("newUSer");
            user.setEmail("newUser@test.com");
    
    				//Spring Data JPA를 이용한 영속화 (save안에 EntityManaer persist호출 존재)
            userRepository.save(user); 
    			
    				//EntityManaer를 이용한 영속화
            entityManager.persist(user); 
        }
    }
    • 영속화 된 상태에서 객체를 수정(setter)하는 경우 수정 쿼리가 발생
      • 영속성 컨텍스트에 dirt Check (변경 감지) 기능
        • 엔티티를 컨텍스트에 로드를 할 때 정보를 스냅샷으로 복사
        • flush, commit, JPQL 실행시 스냅샷-엔티티 값 비교 후 변경되면 수정쿼리 발생
        • 대량의 엔티티를 다룰때 성능저하가 발생의 주의필요
        @Transactional
        public void put(){
        		...
        		//EntityManaer를 이용한 영속화
            entityManager.persist(user); 
        
        		user.setName("newUserAfterPersist");
        }
        
        //실행 결과
        Hibernate: 
            insert 
            into
                user
                (created_at, updated_at, email, gender, name) 
            values
                (?, ?, ?, ?, ?)
        
        Hibernate: 
            update
                user 
            set
                updated_at=?,
                email=?,
                gender=?,
                name=? 
            where
                id=?

3. 준영속 상태

  • 영속화 된 객체를 분리해서 영속성 컨텍스트 밖으로 꺼내는 상태
  • 준영속 상태로 변경하는 메소드
    • detach() : 식별자를 전달함으로 하나의 건만 적용 (flush() → 준영속)
      • detach() 를 사용하면 영속화 된 상태를 flush() 후 준영속 상태로 변경
      TestEntityManager.java ( entityManager.detach 구현체 )
      public <E> E persistFlushFind(E entity) {
      		EntityManager entityManager = getEntityManager();
      		persistAndFlush(entity);
      		Object id = getId(entity);
      		entityManager.detach(entity);
      		return (E) entityManager.find(entity.getClass(), id);
      }
    • clear(), close() : 영속성 컨텍스트로 반영하려는 값들을 모두 초기화 (flush() 없음)
      • 기존에 저장 된 영속 컨텍스트를 반영하려면 flush()가 필요
      @Transactional
      public void put(){
      		...
      
      		entityManager.merge(user);
      		entityManager.flush(); //flush하지 않으면 모두 drop 됌
      		entityManager.clear();
      }
  • 일반적으로 영속화 된 객체를 detach() 사용X
    • Spring Data JPA에서는 detach()존재X
    @Transactional
    public void put(){
        User user = new User();
        user.setName("newUSer");
        user.setEmail("newUser@test.com");
    
        entityManager.persist(user); //영속상태
    
    		//영속 -> 준영속 (detach, clear, close)
        entityManager.detach(user);  
        entityManager.clear();
        entityManager.close();
    
        user.setName("newUserAfterPersist"); //준영속상태
    }
    
    //실행 결과
    Hibernate: 
        insert 
        into
            user
            (created_at, updated_at, email, gender, name) 
        values
            (?, ?, ?, ?, ?)
    
    //update 실행X
  • merge() 를 이용하면 준영속 상태에 데이터 반영 가능
    • persist, mergeJpaRepository에서 제공하는 save 명령어로 해결
      • 그래서 JpaRepository에선 반영하는 시점에 save를 사용

4. 삭제 상태

  • 엔티티를 사용할 수 없는 상태로 변경
  • 데이터베이스, 영속성 컨텍스트에서 모두 삭제 (merge 오류 발생)
User user1 = userRepository.findById(1L).get();
entityManager.remove(user1);

user1.setName("marrrrrrrrrrrrrr");
entityManager.merge(user1);
//실행 결과
org.hibernate.ObjectDeletedException: deleted instance passed to merge: [com.example.bookmanager.domain.User#<null>]
java.lang.IllegalArgumentException: org.hibernate.ObjectDeletedException: deleted instance passed to merge: [com.example.bookmanager.domain.User#<null>]
	at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:797)
	at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:780)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)