Spring

JPA 리 오류들 체크 리스트

sehunbang 2024. 3. 6. 19:00

오해 풀기

  • 실습하다보면 내가 생각한대로 JPA가 동작하지 않을때가 많죠???

 

 

1. 쿼리 최적화

 

JPA 가 1차캐시(영속성 컨텍스트) 를 통해 필요없는 쿼리는 날라가지 않도록 쿼리를 최적화 해준다고 하는데….

 최적화가 안되고 Insert, Delete 쿼리가 다 DB로 날라가는 로그가 찍히는데 왜그런건가요?

 

쿼리 최적화를 발생시키기 위해서는 아래 3가지를 확인해봐야 합니다.

 

 

 

1. 먼저 해당 함수나 클래스가 Transaction 안에 포함되고 있는지 봐야합니다.

  1. @Transactional 으로 함께 감싸져 있어야만 쿼리 최적화가 동작합니다.
  2. Transaction 으로 포함되어있지 않으면 repository 메소드 내부에서만 Transcation 이 최적화됩니다.
  3. Transaction Propagation (전파) 전략 체크해봐야합니다. (심화)

 

 

2. 두번째로, 해당 엔티티의 ID 식별자 생성전략을@GeneratedValue(strategy = GenerationType.IDENTITY)로 사용한건 아닌지 확인해봐야 합니다.

  1. GenerationType.IDENTITY 로 키필드가 설정되어 있으면 데이터베이스에 실제로 저장을 해야 유일한 식별자를 구할 수 있으므로 Insert 쿼리가 즉시 데이터베이스에 전달됩니다.
  2. GenerationType.SEQUENCE 로 설정하길 추천드립니다. 이렇게하면 DB에 select nextval('thread_seq') 쿼리만 날라갑니다.
  3. GenerationType.AUTO 로 설정해서 DB에서 선호하는 방식으로 자동적용시켜줄 수도 있습니다.

 

3. 마지막으로, orphanRemoval() 영속성 전이에 의해 자식의 삭제작업이 이루어지는건 아닌지 확인해봐야합니다.

  1. 이런경우 1차캐시 최적화가 아닌 삭제쿼리가 쓰기지연 저장소에 저장되어 동작하게 되므로 후처리로 발생하게 됩니다.

 

 

 

save() 를 수행했는데 Insert, Select 쿼리가 날라갔어요!

ID(식별자 키) 값이 존재하는 Entity를 save() 하면 업데이트할 필드가 있는지 확인하기 위해 Select 쿼리를 먼저 날려서 조회 해옵니다.

 

save() 를 수행하는 코드:

@Transactional
@Override
public <S extends T> S save(S entity) {

  Assert.notNull(entity, "Entity must not be null");

  if (entityInformation.isNew(entity)) {
    em.persist(entity);
    return entity;
  } else {
    return em.merge(entity);
  }
}
  1. 즉, save(S) 메소드는 엔티티에 식별자 값이 없으면(null이면) 새로운 엔티티로 판단해서 EntityManager.persist를 호출하고,
  2. 2식별자 값이 있으면 이미 있는 엔티티로 판단해서 EntityManager.merge()를 호출합니다.
  3. EntityManager.merge() 를 수행할때 수정할 필드가 없는지 확인하기 위해 식별자 값을 가지고 Select 쿼리를 먼저 날려서 조회 해오고,
  4. 조회된 필드들을 엔티티와 비교해서 수정된 사항이 있으면 UPDATE 쿼리를 날리고, 모든 필드가 같으면 아무 쿼리도 날리지 않습니다.

 

Test 클래스에 @

 

Spring 통합 테스트 vs JPA 슬라이스 테스트

위 2개를 비교했을때 통합테스트는 아래와 같은 단점이 있기 때문에 가능하다면 슬라이스 테스트를 추천드립니다.

 

  • 실제 구동되는 애플리케이션의 설정, 모든 Bean을 로드하기 때문에 시간이 오래걸리고 무겁다.
  • 테스트 단위가 크기 때문에 디버깅이 어려운 편이다.
  • 결과적으로 웹을 실행시키지 않고 테스트 코드를 통해 빠른 피드백을 받을 수 있다는 장점이 희석된다.

 

Spring 통합 테스트 @

@SpringBootTest
// SpringApplication 띄울때의 빈들을 모두 생성해줍니다.
@Transactional
// 테스트 메소드들이 모두 트랜잭션에 포함되어 최적화 되도록 합니다. 
// 테스트 대상 함수의 실행환경에서는 Transaction이 안걸려 있을 수 있으니 실무에 사용시 주의
@Rollback(value = false) // 테스트 데이터가 롤백되지 않고 실제 DB에 반영되도록 합니다.

 

 

JPA 관련 빈 만 사용하는 JPA 슬라이스 테스트 @

@DataJpaTest
// SpringDataJpa 테스트에 필요한 빈들만 생성해줍니다.
@Transactional
// 테스트 메소드들이 모두 트랜잭션에 포함되어 최적화 되도록 합니다. 
// 테스트 대상 함수의 실행환경에서는 Transaction이 안걸려 있을 수 있으니 실무에 사용시 주의
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
// 테스트 용 DB를 따로 설정하지 않고 main 환경 DB를 그대로 사용하도록 합니다.
@Import(JPAConfiguration.class)
// JPAQueryFactory와 같이 테스트시 필요한 빈들을 정의해놓은 Configuration 설정합니다.
@Rollback(value = false)
// 테스트 데이터가 롤백되지 않고 실제 DB에 반영되도록 합니다.