이전에 Nest.JS로 개발하다가 중복된 값이 있으면 Update 해주고 아닐경우 Insert를 해야되는 케이스가 있었는데
Upsert나 save를 사용하지 않고 로직으로 find해서 If-Else문으로 Update, Insert를 날려서 처리한적이 있었다.
Upsert나 Save로 처리했으면 코드가 좀 더 간결해지고 DB커넥션도 줄여서 성능향상도 됬을텐데,,,하고 아쉬워하던 찰나에
비슷한 케이스로 개발을 할 일이 생겼다!
뉴스의 기사에 좋아요, 싫어요 리액션 기능을 추가하는 케이스이였다.
이번엔 무조건 로직으로 처리하지 않고 Upsert나 Save로 처리해야겠다고 생각했다.
그럼 우선 Upsert랑 Save에 주요 차이점 대해 알아보면
Upsert는 데이터베이스에서 레코드를 업데이트하거나 삽입하는 데 중점을 둔 반면, (부분만 레코드)
Save는 모든 종류의 데이터를 모든 종류의 저장소에 저장한다. (엔티티 통으로)
그리고 Save는 저장한 결과값을 Select를 한번 더 한다.
관련 Save 내용에 대해 이전에 포스팅 했으니 읽어보면 도움이 될 것 이다.
https://ilikezzi.tistory.com/11
[TypeOrm] 차이를 알고쓰자 save() , insert() , update()
오늘은 TypeOrm method인 save(), insert(), update() 에 대해서 알아보려고 한다. 얼핏보기에는 비슷해서 아무거나 써도 될 것 같은데 이게 별거 아닌 것 같아도 성능에도 영향을 끼친다. 우선 typeorm 공식
ilikezzi.tistory.com
아무튼 해당 케이스에서는 성능을 고려하면 TypeOrm으로 Upsert가 적합하다고 생각했다.
TypeOrm에 Upsert가 추가된지는 아직 1년도 되지 않았다고 한다.
우선 target_uuid 와 user_uuid 값이 동일하면 update를 해야했다.
이렇게 Upsert가 정상적으로 작동됬겠지 해서 DB를 확인해 보았다.
DB를 확인해보니 Update처리가 안되고 Insert처리가 되버렸다...(?)
찾아보니 중복일 시 업데이트 되는값은 무조건 Unique한 값이여야 한다해서 Entity 설정도 해주었다.
이젠 되겠지 했는데 여전히 그대로 였다.
이번엔 아예 인덱스를 걸어서 처리해 보았다.
이래도 여전히 DB에 Insert만 되었다...
ON DUPLICATE KEY UPDATE
`target_uuid`=VALUES(`target_uuid`),
`user_uuid`=VALUES(`user_uuid`),
`target_type`=VALUES(`target_type`),
`user_name`=VALUES(`user_name`),
실행되는 쿼리문을 확인 해보았더니 중복 처리부분이 아예 이상하게 실행 되고 있었다.
내가 처리한 값 외에 "target_type"과 'user_name' 등등 여러개가 이상하게 들어가고 있었다.
공식문서를 보면 Upsert 실행 시 "ON CONFLICT" 방식으로 중복일 시 업데이트 처리가 이뤄져야 했다.
공식문서처럼 중복시 업데이트 시키는 값만 넣었더니 업데이트는 커녕 Null값으로 Insert가 되버렸다..
Upsert 메서드에 대해서 빠뜨린것이 있을까 싶어서 다시 확인 하였다.
Upsert( entityOrEntities, conflictPathsOrOptions) 로 구성되어 있다.
entityOrEntities: 삽입하거나 업데이트할 엔티티이다.
이 경우 target_uuid, user_uuid 및 data 속성이 있는 객체를 넣어주었다.
conflictPathsOrOptions: 충돌 조건을 정의하는 열 이름의 배열이다.
이 경우 "target_uuid" 및 "user_uuid"라는 두 항목이 포함된 배열을 넣어주었다.
다 정상적으로 해줬는데 뭐가 문제일까....
찾아보니 mysql 최신버전이 아니면 지원을 안해준다는 얘기도 있고, postgre에서만 작동되는 쿼리라는 얘기도 있어서
임시방편으로 유사한 기능을 제공해주는 save()를 사용하기로 했다.
Save는 이 처럼 Entity전체를 넣어주면 된다.
기본적으로 해당 엔티티의 pk를 식별자로 사용해서 중복일 시 Update를 한다.
하지만 내가 원하는 "target_uuid" 및 "user_uuid" 두 값은 pk가 아니였다.
그래서 pk가 아닌 값으로 식별이 가능한지 찾아보았다.
기본 키 이외의 고유한 제약 조건으로 save 메서드를 사용할 때 엔터티의 새 인스턴스를 만들 때 기본 키 열에 대한 값을 설정하지 않는 것이 중요합니다. 기본 키에 대한 값이 설정되면 TypeORM은 다른 열에 대한 고유 제약 조건을 고려하는 대신 해당 기본 키 값으로 레코드를 업데이트하려고 시도합니다.
pk값이 없을 때에만 다른 식별자를 만들수있는데
지금 사용중인 엔티티의 pk값인 "id"는 last_id나 여러군데에 사용중이여서 pk 제약조건을 해제 할 수 없는 상황이다.
따라서 Save()도 사용을 못하게 되었다.
최후의 방법으로 쿼리빌더로 해결하기로 했다.
async upsert(ReactionEntity) {
await this.repository
.createQueryBuilder()
.insert()
.into(ReactionEntity)
.values(ReactionEntity)
.orUpdate({
conflict_target: ['target_uuid', 'user_uuid'],
overwrite: [ReactionEntity],
})
.execute();
}
다행히 쿼리빌더로 코드를 돌리니 정상적으로 DB에 Insert가 아닌 Update가 되었다.
나 말고도 많은 사람들이 Upsert를 시도했지만 작동하지 않았다는 글이 수두룩하더라.
쿼리빌더로 해결은 했지만 왜 작동이 안되는지 너무 답답하다...
다음에 다시 Upsert로 해결해서 포스팅을 해야겠다.
참조:
https://github.com/typeorm/typeorm/issues/1090
https://stackoverflow.com/questions/46745688/typeorm-upsert-create-if-not-exist
'Web > TypeOrm' 카테고리의 다른 글
[TypeORM] 밤 12시, 수만 명의 출석체크에 대응한 동시성 문제 해결기 (1) | 2024.03.20 |
---|---|
[TypeOrm] loadRelationCountAndMap() - 관계된 데이터의 갯수를 쉽게 세는 방법 (0) | 2023.11.05 |
[TypeOrm] IN조건으로 배열 검색 "Typeorm find where in array" (0) | 2023.02.15 |
[TypeOrm] timezone: 'z' charset UTC시간 설정 (0) | 2023.01.28 |
[TypeOrm] 차이를 알고쓰자 save() , insert() , update() (0) | 2023.01.25 |