Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

마로의 개발일지

Spring-data-MongoDB upsert시 duplicateKeyException 해결하기 본문

알게된 것

Spring-data-MongoDB upsert시 duplicateKeyException 해결하기

maro0201 2023. 5. 21. 21:05
com.mongodb.MongoException: org.springframework.dao.DuplicateKeyException: E11000 duplicate key error collection

어느 날 모니터링 채널에 해당 에러가 올라왔다. 이전까지 본 적이 없는 에러였는데 찾아보니 key 값이 중복으로 Insert 되어 발생한 에러라고 했다. 다음과 같이 서비스를 구현하고 테스트했을 땐 잘 되던 로직이 간헐적으로 해당 에러를 반환하고 있었다. 대상 데이터를 찾고 상태를 변경한 후 다시 저장할 때 Update가 아닌 Insert가 되어 발생한 것 같았다. 분명 @Transactional로 인해 하나의 트랜젝션 안에서 찾은 데이터기 때문에 당연히 Update로만 실행이 될 줄 알았는데 내가 모르는 다른 이유가 있는 것 같았다. 해당 이유는 알아내지 못했지만 문제 해결을 해야 했기 때문에 Entity객체에 org.springframework.data.domain 패키지의 Persistable을 구현하고 isNew() 메서드를 오버라이딩하여 항상 false를 반환하게 해주었더니 해결되었다. 

사실 mongoDB 데이터를 불러와서 수정하는 것 자체가 좋지 않은 것 같지만... 로직상에 구현으로 인해 어쩔수 없이 해당 방법을 통해 처리했다. 이렇게 사용하는 경우가 거의 없다 보니 해결 방법을 spring-data-jpa의 해결방법에서 찾게 되었다. 같은 spring data니까 같은 방법을 통해 해결이 가능하지 않을까 하는 생각에 시도해 봤는데 배포한 후 해당 에러가 발생하지 않는 것을 보니 동일하게 적용이 가능한 것 같다. 지금은 이렇게 단순 처리를 통해 해결했지만 다음엔 애초에 이런 수정이 필요하지 않게 서비스 로직을 수정하거나 duplicateKeyException이 발생하는 원인을 파악해서 처리하는 게 더 좋을 것 같다는 생각이 들었다. 그래도 해당 문제에만 시간을 할애할 순 없으니... 이번엔 이대로 넘어가는 걸로...

// build.gragle에 추가한 dependency
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive'
}

// Entity 객체
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Document(collection = "test")
public class MongoTest {

    @Id
    private String id;
    private LogCode code;
    private Object data;

    public void changeCode(LogCode code) {
        this.code = code;
    }
}

// 서비스
@Service
@AllArgsConstructor
public class MongoTestService {

    private final MongoTestRepository mongoTestRepository;
    
    @Transactional
    public void saveData() {
        // Id로 해당 데이터 찾기
        MongoTest testData = mongoTestRepository.findById("TEST");
        // code 변경
        testData.changeCode(LogCode.END);
        // save(Key값(Id)이 없으면 Insert 존재하면 Update)
        mongoTestRepository.save(testData).subscribe();
    }
}

// 구현한 Repository
public interface MongoTestRepository extends ReactiveCrudRepository<MongoTest, String>  {

}

수정한 Entity

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Document(collection = "test")
public class MongoTest implements Persistable {

    @Id
    private String id;
    private LogCode code;
    private Object data;

    public void changeCode(LogCode code) {
        this.code = code;
    }

    @Override
    public boolean isNew() {
        return false;
    }
}
Comments