ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JPA] JpaRepository save() 메서드 주의 사항
    Spring 2021. 2. 17. 17:09
    반응형

     

    오늘은 JpaRepository 인터페이스의 메서드 중 주의하여 사용해야 할 save() 메서드에 대해 알아보겠습니다.

     

    우리가 ORM 프레임워크인 JPA와 Spring 프레임워크를 연계하여 사용할 때 Spring Data에서 지원하는 JpaRepository 인터페이스를  자주 사용하는데요, 실제 동작하는 구현체인 SimpleJpaRepository는 @Repository 어노테이션이 붙어있기 때문에 스프링이 실행될 때 AnnotationApplicationContext에 의해 스프링 컨테이너에 빈으로 등록이 됩니다.

     

    이 모든 것을 스프링이 해주기 때문에 개발자는 JpaRepository를 확장하는 인터페이스만 만들어서 Repository로 사용하면 되는 것입니다. (스프링은 알면 알수록 애플리케이션을 개발하기에 참 편리한 도구인 것 같습니다. ㅎㅎ)

     

    우리가 이렇게 편리하게 사용할 수 있지만, 반대로 이러한 라이브러리는 내가 손수 구현하지 않았기 때문에 사용할 때 어떤 위험이 있을지 모르고 내가 생각한대로 되겠지라는 생각으로 사용하다보면 버그가 발생할 수 있습니다. SimpleJpaRepository 구현체의 save() 메서드 같은 경우인데요, 코드를 직접 보면서 어떤 문제가 있을 수 있는지 확인 해보겠습니다.

     

    ** SimpleJpaRepository.save(S entity) 메서드 **

    @Transactional
    @Override
    public <S extends T> S save(S entity) {
    
    	if (entityInformation.isNew(entity)) {
    		em.persist(entity);
    		return entity;
    	} else {
    		return em.merge(entity);
    	}
    }

     

    save() 메서드 내부 구현에서는 파라미터로 넘어온 entity가 새로운 엔티티라면 EntityManager의 persist() 메서드를 호출하고 그렇지 않다면 merge() 메서드를 호출 합니다. merge는 보통 detach된 엔티티를 다시 영속 상태로 만들기 위해 사용하는데, 파라미터로 넘어온 entity가 영속성 컨텍스트 1차 캐시에 있는지 확인 후 없다면 DB에 id를 기준으로 select 쿼리를 날려서 조회합니다. DB에서 조회가 되어 1차 캐시에 엔티티가 저장이 되면 트랜잭션 커밋 시점에 파라미터 entity의 값과 1차 캐시에 저장되어 있는 entity의 값을 비교하여 다른 점이 있을 경우 update 쿼리가 발생합니다. 반대로, DB에 해당 값이 없을 때 insert 쿼리가 발생합니다.

     

    즉, 나는 save() 메서드를 통해 단순히 저장하려고 해도 update 쿼리가 발생할 수 있다는 것입니다.

     

    그렇다면 entity가 새로운 엔티티인지는 어떻게 구별할까요?

    바로 entity 식별자(ID)의 상태입니다. 식별자가 객체 타입(Long, String 등...)일 때는 null, 기본 타입(int, long 등..)일 때는 0 이면 새로운 엔티티로 인식합니다. 식별자에 @GenerateValue 애노테이션을 설정하였다면, 데이터베이스에 식별자 생성을 위임하기 때문에 save를 호출하는 시점에 새로운 엔티티로 인식하여 우리가 원하던대로 persist가 호출 되겠지만, @Id 만 사용해서 식별자를 직접 할당 하였다면, 이는 save 호출 시점에 새로운 엔티티가 아니므로 merge를 호출하게 됩니다.

     

    만약에 DB에 해당 식별자로 하는 데이터가 이미 들어가 있었다면?

    해당 데이터는 파라미터 entity의 값으로 모두 update 되어버립니다.

     

    그렇다면 이 문제를 어떻게 예방할 수 있을까요?

    @Id만 쓰는 엔티티는 Persistable<ID> 인터페이스를 구현해서 isNew()를 오버라이딩 함으로써 판단 로직을 상황에 맞게 변경하시면 됩니다. 

     

    docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Persistable.html

    반응형
Designed by Tistory.