ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JPA - save 메서드에 대하여🤯 (엔티티 생명주기, isNew(), @GeneratedValue, persist(),merge())
    CS/Spring 2024. 6. 26. 03:26

    save메서드를 보기 전에 Entity 생명주기에 대해 알아보자

    Entity 생명주기

    • 비영속 상태 (Transient) : 엔티티가 새로 생성되었지만 아직 영속성 컨텍스트에 추가되지 않은 상태. 데이터베이스와 관련 x
    • 영속 상태 (Persistent): 엔티티가 영속성 컨텍스트에 포함되어 있으며, 변경 사항이 자동으로 데이터베이스에 반영된다.
    • 분리된 상태 (Detached): 엔티티가 한때 영속성 컨텍스트에 포함되어 있었지만, 현재는 영속성 컨텍스트의 관리에서 벗어난 상태.
    • 삭제된 상태 (Removed): 엔티티가 영속성 컨텍스트에 포함되어 있지만, 데이터베이스에서 삭제될 예정인 상태.

     

     

    SAVE() 메서드

    JPA를 사용해 테이블에 값을 insert할 때 JpaRepository의 save메서드를 사용한다.

    productRepository.save(product);

     

    save 메서드를 따라 들어가보면, 다음과 같은 코드를 볼 수 있다.

    @Override
    @Transactional
    public <S extends T> S save(S entity) {
    
    	Assert.notNull(entity, "Entity must not be null");
        
    	if (entityInformation.isNew(entity)) {
    		entityManager.persist(entity);
    		return entity;
    	} else {
    		return entityManager.merge(entity);
    	}
        
    }

     

    isNew() 메서드

    save() 메서드가 실행이 되면, 먼저 isNew()메서드를 통해 전달된 Entity가 새로운 엔티티인지, 기존 엔티티인지 확인한다.

    새로운 엔티티인지 아닌지는 일반적으로 식별자(ID)를 통해 구분한다.

    식별자가 null 이거나 기본값(0 또는 "")일 경우 새로운 엔티티로 판단한다.

     

    그럼 식별자(ID)는 언제 생성이 될까?

     

    ID생성 전략에 따라 생성 시기가 달라진다.

    자동 생성 전략 (AUTO, IDENTITY, SEQUENCE 등):

    - IDENTITY 전략 :
    ID가 데이터베이스에 엔티티가 실제로 삽입될 때 생성. 즉, persist 메서드를 호출한 후 트랜잭션이 커밋되거나 flush가 일어날 때 데이터베이스에 INSERT 쿼리가 실행되며, 그 시점에 ID가 할당되는 방법.
    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    private Long id;​

    - SEQUENCE 전략 :
    ID가 데이터베이스 시퀀스를 사용하여 엔티티가 영속성 컨텍스트에 추가될 때 미리 할당됨. 즉, persist 메서드를 호출할 때 바로 시퀀스 값을 조회하여 ID를 할당하는 방법.
    @Id 
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "my_seq") 
    @SequenceGenerator(name = "my_seq", sequenceName = "my_sequence", allocationSize = 1) 
    private Long id;​

    - TABLE 전략 :
    테이블을 사용하여 ID를 생성하는 방식으로, persist 메서드를 호출할 때 ID가 할당됩니다.
    @Id 
    @GeneratedValue(strategy = GenerationType.TABLE) 
    private Long id;​

    직접 할당:
    엔티티를 생성할 때 직접 ID를 할당하는 경우, persist 메서드를 호출하기 전에 ID가 이미 설정된다.
    @Id private Long id; // 생성 시 직접 ID 할당 
    MyEntity entity = new MyEntity(); 
    entity.setId(1L); // 이렇게 직접 생성만 하고 persist를 실행하기 전의 경우는 분리된 상태!
    entityManager.persist(entity);

     

     

    이 내용을 공부하기 전까지는 엔티티를 구분하는 식별자와 테이블 기본키로 설정했던 @Id가 같은건줄 몰랐다.(나만..?🫣)

    1. 그냥 엔티티 클래스를 만들때는 @Id를 꼭 만들어야 한다해서 그런줄만 알았는데, 엔티티를 구분하기 위해 반드시 필요한 속성이였던 것

    2. 그냥 auto_increment 설정인줄만 알고 항상 쓰던 @GeneratedValue(strategy = GenerationType.IDENTITY)가 ID 생성 시기를 결정하는 설정이였던 것.

     

    persist() 메서드

    save메서드에 전달된 엔티티가 isNew() 메서드를 통해 판단했을 때 새로운 엔티티라고 판단이 되면 persist()메서드를 호출한다.

    persist()메서드가 호출되면 크게 3가지의 과정을 거친다. 

        • 새로운 영속 상태의 엔티티를 생성
        • 쓰기 지연 쿼리 저장소에 INSERT 쿼리문을 저장
        • flush() 메서드를 호출

     

    merge() 메서드

    save메서드에 전달된 엔티티가 isNew() 메서드를 통해 판단했을 때 이미 존재하는 엔티티라고 판단이 되면 merge()메서드를 호출한다.

    이미 존재하는 엔티티에는 두가지 경우가 있다.

       

       1. 영속 상태의 기존 엔티티인 경우 :

          

           기존의 영속 상태의 엔티티 내용을 전달된 엔티티의 내용으로 업데이트한다.

           업데이트 시 변경 사항이 감지되면, UPDATE 쿼리문이 쓰기 지연 쿼리 저장소에 저장된다.
           모든 과정이 끝나면 업데이트 사항이 반영된 기존 엔티티가 반환된다.

       2. 분리된 상태의 기존 엔티티인 경우 : 

           

           새로운 엔티티를 생성해 전달된 엔티티 내용을 반영하고(엔티티 복사), 1차 캐시에 저장한다.
           모든 과정이 끝나면 1차 캐시에 저장된 영속 상태의 엔티티를 반환한다.
           이때 전달된 엔티티는 계속 분리된 상태로 남아있게 되지만, 분리된 상태는 데이터 베이스에 반영되지 않으므로 무관하다.

     

    merge() 메서드가 종료되고 트렌젝션이 종료되기 직전에 flush() 메서드가 실행된 후 commitI()메서드가 호출되어 db에 변경 내용이 반영된다.

     

    💡flush() vs commit()

    flush () :
    영속성 컨텍스트에 있는 변경 사항(추가, 수정, 삭제)을 데이터베이스에 반영
    쓰기 지연 SQL을 데이터베이스로 보냄.

    commit() :
    트랜잭션의 작업을 영구적으로 데이터베이스에 반영하고, 트랜잭션을 종료
    이후에는 롤백이 불가능합니다.

    결론적으로
    flush는 영속성 컨텍스트의 변경 사항을 데이터베이스에 동기화하는 역할을 하며, commit은 트랜잭션 내에서 수행된 변경 사항을 영구적으로 데이터베이스에 반영하는 역할을 한다.
    쉽게말해 flush는 쿼리 실행만 하고 실제 db에는 반영되지 않지만, commit은 변경 사항이 실재 db에 반영이 된다.
Designed by Tistory.