[ERR] r2dbc saveAll() 시 isNew() 판단 못하는 문제

아무튼 간에·2023년 1월 30일
0

에러/오류

목록 보기
9/9

개발환경

OS: Windows 11
IDE: IntellJ IDEA 2022.2.4 (Ultimate Edition)
Java: 1.8

Framework: Springframework.boot version: 2.4.3
Dependencies:

implementation "org.springframework.boot:spring-boot-starter-data-r2dbc"
implementation 'dev.miku:r2dbc-mysql:0.8.2.RELEASE'
implementation group: 'javax.persistence', name: 'javax.persistence-api', version: '2.2'

...

* 아래 코드는 예시 코드로 변경한 것임


상황

  • 제목 그대로 1개 이상의 데이터 리스트를 업데이트 하기 위해 saveAll() 메소드를 사용했는데 새로운 객체인지 업데이트 대상 객체인지 판단하지 못함. 계속 insert만 하거나 update만 하려고 해서 unique 관련 에러 등이 발생함.
  • 스키마 변경 불가

시도한 방법들

  • Entityimplements Persistable 후 직접 isNew()를 재정의
  • @Value 어노테이션 사용
  • @Generated 어노테이션 사용
  • 삽입/업데이트 리스트에서 하나씩 값 조회 후 isNew() 설정
  • 테이블 전체 조회 후 삽입/업데이트 리스트와 비교하여 isNew()설정

점점 성능을 외면하게 되는 시도들까지 하게 됨..


원인

처음 테이블 세팅했을 때


해결

결론부터 말하면,
프로젝트 상황 상 스키마를 변경할 수 없고, 반드시 varchar로 사용해야만 했으므로
일단은 서비스 로직에서 직접(...) insert 대상과 update 대상을 구분시키고 update 대상은 파생 쿼리를 따로 만들어서 타게 했다.
성능 이슈가 예상됨.... 흑흑

참고: [Spring Data JPA] save와 saveAll의 성능 차이에 대한 실험과 결과!(스프링 프록시, @Transactional)
→ 데이터가 많을수록 saveAll의 성능이 좋음

> 파생쿼리

FruitRepository.java

import org.springframework.data.r2dbc.repository.Modifying;
import org.springframework.data.r2dbc.repository.Query;
import org.springframework.data.r2dbc.repository.R2dbcRepository;
import reactor.core.publisher.Mono;

public interface FruitRepository extends R2dbcRepository<FruitEntity , String> {
    @Modifying
    @Query("UPDATE fruit_entity SET type = :type, fruit_class = :fruitClass where name = :name")
    Mono<FruitEntity> setFruitFor(String type, String fruitClass, String name);
}
  • set[대상]for(): 레포지토리 메소드명 규칙. 지켜서 써주는 게 좋다.
  • @Modifying: 파라미터 바인딩해서 수정하겠다
  • @Query: 실행할 쿼리
    → 규칙: 실제 컬럼명 = :파라미터명
    ex) fruit_class = :fruitClass

참고: R2DBC Repositories 14.2.1 Modifying Queries

> insert/update 대상 구분

@Service SyncService.java

public Mono<Boolean> saveAllFruit(List<FruitEntity> fruitEntity) {
    try {
        for(FruitEntity i: fruitEntity){
            FruitRepository.findByName(i.getName())
                    .flatMap(found -> fruitRepository.setMmsInfoFor(i.getType(), i.getFruitClass(), i.getName()))
                    .switchIfEmpty(fruitRepository.save(i)).subscribe();
        }
    }catch (Exception e){
        e.printStackTrace();
        return Mono.just(false);
    }
    return Mono.just(true);
}
  • flatMap(): i번째 데이터가 이미 DB에 있을 경우 파생쿼리 태움
  • switchIfEmpty(): i번째 데이터가 DB에 없을 경우 insert 진행

이때까지만 해도 WebFlux 기반 리턴타입 작성하는 게 너무 어려워서 모든 걸 검색해서 작성했다.
서비스 코드 참고: Find/Modify/Save or Upsert with Spring Data R2DBC


참고

profile
armton garnet

0개의 댓글