back end/java

Spring Data JPA에서 Insert 전에 select query를 하는 이유

노루아부지 2024. 2. 2. 17:25

 

개요

 

JPA로 API 서버 개발을 하던 중 insert query에서 매번 select query가 발생하여 엄청난 성능 저하가 발생했습니다.

도대체 왜 이런 것인가? select를 하지 않게 바꿀 수는 없는가? 찾아봤습니다.

 

 

현상 확인

 

1. User 생성

@Entity
@Table
@Getter
@Setter
public class User {
  @Id
  private String userId;
  private Integer age;
}

 

2. UserRepository 생성

public interface UserRepository extends JpaRepository<User, String> {

}

 

 

3. 테스트 클래스 생성

@SpringBootTest
class UserTest {
  @Autowired
  private UserRepository repository;

  @Test
  void test() {
    User user = new User();
    user.setUserId("test");
    user.setAge(20);

    repository.save(user);
  }
}

 

 

4. 결과 확인

user select, insert

 

 

 

save() 처리 방식

 

그렇다면 도대체 왜 이렇게 동작하는 것일까요?

 

CrudRepository 구현체인 SimpleJpaRepository를 보면 save는 아래와 같이 처리됩니다.

@Transactional
@Override
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 = true)이면 persist
  • 아니면 merge
    • merge는 persist → detach  → persist 상태가 될 때를 의미

 

 

isNew

 

결론은 insert를 할 때 isNew가 false이기 때문에 select query를 하는 것이었습니다.

따라서 해결 방법은 isNew가 true가 될 수 있도록 하는 것인데요

현재 isNew가 true가 되는 기준을 찾아봤습니다.

결론은 entity의 ID가 존재하면 false 라는 것입니다.

아래는 isNew의 동작을 확인한 과정입니다.

 

1) SimpleJpaRepository

SimpleJpaRepository

 

2) JpaMetamodelEntityInformation

JpaMetamodelEntityInformation

 

3) AbstractEntityInformation

AbstractEntityInformation

 

 

 

IDENTITY를 사용하는 경우?

 

그렇다면 아래와 같이 IDENTITY를 사용하는 ENTITY는 어떻게 될까요?

@Entity
@Table
@Getter
@Setter
public class History {
  @Id
  @GeneratedValue(strategy=GenerationType.IDENTITY)
  private Long seq;
  private String value;
}
@SpringBootTest
class HistoryTest {
  @Autowired
  private HistoryRepository repository;
  @Test
  void test() {
    History history = new History();
    history.setValue("test");

    repository.save(history);
  }
}

 

다음과 같이 select query가 발생하지 않습니다.

history insert

 

자동증가값을 사용해서 entity의 ID가 null이기 때문입니다.

즉, ID에 자동증가값(Auto Increament)을 사용하지 않는 테이블의 경우 save를 호출하는 시점에 ID의 값이 존재하기 때문에 isNew가 false가 되어 select query를 하는 것입니다.

 

 

 

 

 

해결 방법

 

해결 방법은 간단합니다 isNew가 true가 되도록 변경하면 됩니다.

기존 isNew = true가 되는 조건은 ID가 존재하는 여부였는데, 이 조건을 다음과 같이 변경합니다.

  • 영속성 콘텍스트에서 조회되지 않았을 때
  • 영속 상태가 되기 전

 

위 조건을 맞추기 위해 다음과 같은 절차로 코드를 변경합니다.

  • isNew라는 상태값 변수를 만들고 default 값을 true로 선언
    • 이렇게 하면 Entity를 저장하기 위해 새로운 객체를 생성하면 기본 값인 true가 됩니다.
  • @PrePersist를 통해 JPA Entity가 영속 상태가 되기 전에 false로 변경
  • @PostLoad를 통해 영속성 콘텍스트에 조회된 직후, refresh를 호출한 직후에 false로 변

 

 

 

 

참조

 

@PrePersist, @PreUpdate 애너테이션 활용하기

<br /><br />

junhyunny.github.io

 

 

[JPA] 리스너 - 엔티티의 생명주기에 따른 이벤트 처리

리스너 모든 엔티티를 대상으로 언제 어떤 사용자가 삭제를 요청했는지 모두 로그로 남겨야 하는 요구사항이 있다고 가정하자. 이때 애플리케이션 삭제 로직을 하나 하나 찾아가며 로그를 남기

ttl-blog.tistory.com

 

 

Spring Data JPA에서 Insert 전에 Select 하는 이유

이전 글에서 bulk insert 처리를 할 수 있는 방법 중에 하나가 @Id 값 알고 있는 경우라고 했었는데요. 실제로 잘 되는지 확인하는 과정에 발생한 문제 내용을 공유합니다.

kapentaz.github.io

 

 

Spring Data Jpa Insert 할 때 Select가 나가네..

문제 상황 설계한 Entity의 id가 Auto Increament값이 아니다. 생성자가 호출되는 시점에 fk의 조합으로 생성된다. makeReservedSeatId 함수에서 만들어진다. @Entity @Table(name = "reserved_seat") public class ReservedSeat

taesan94.tistory.com

 

 

 

 

 

1. 아래와 같이 User 테이블과 History 테이블이 있다고 가정합니다.

@Entity
@Table
@Getter
@Setter
public class History {
  @Id
  @GeneratedValue(strategy=GenerationType.IDENTITY)
  private Long seq;
  private String value;
}

 

 

 

2. 아래와 같이 테스트를 작성합니다.

@SpringBootTest
class HistoryTest {
  @Autowired
  private HistoryRepository repository;
  @Test
  void test() {
    History history = new History();
    history.setValue("test");

    repository.save(history);
  }
}
@SpringBootTest
class UserTest {
  @Autowired
  private UserRepository repository;

  @Test
  void test() {
    User user = new User();
    user.setUserId("test");
    user.setAge(20);

    repository.save(user);
  }
}

 

 

728x90
loading