본문 바로가기

Troubleshooting

SQLiteDatabase.insertWithOnConflict()에서 API Level에 따라 반환값이 달라질 수 있는 문제

데이터베이스에 데이터를 삽입하는 과정에서 insertWithOnConflict(String, String, ContentValues, int) 메서드를 사용할 경우, 유의해야 할 사항이 있습니다.

해당 메서드는 데이터를 삽입할 때 충돌이 발생할 경우, 충돌을 어떻게 처리할 지 지정하고 싶을 때 사용하는데요, 이 때 사용하는 충돌 처리 인자에 따라 메서드의 반환값이 달라질 수 있는 문제가 있습니다.


개발자 문서에 나와있는 메서드 설명 중 반환값에 대한 설명을 보면,


the row ID of the newly inserted row OR the primary key of the existing row if the input param 'conflictAlgorithm' = CONFLICT_IGNORE OR -1 if any error


이라 되어있습니다. 


위의 설명을 참고하여 메서드의 반환값을 정리해보자면,

  • 삽입 성공시: 삽입한 행의 row ID 반환
  • 삽입 실패시:
    • conflictAlgorithm을 CONFLICT_IGNORE 로 설정한 경우: 충돌이 발생한 열의 Primary key 반환
    • 그 외의 경우 -1 을 반환
으로 요약할 수 있습니다.

그런데, 삽입 실패시 반환하는 반환값에 문제가 있습니다. 우선 sqlite3 C API에서는 충돌이 발생했을 때 conflict algorithm 을 IGNORE로 설정한다 해서 충돌이 발생한 행의 Primary Key를 반화는 기능이 없습니다. 그리고, 만약 Primary Key가 INTEGER가 아닌 TEXT를 사용하거나, 여러 필드를 묶어 Primary Key로 사용하는 경우 반환값을 추정하기 어렵습니다.

결정적으로, 위 메서드에서 반환하는 반환값이 API Level에 따라 다릅니다.
다음 코드를 보도록 하죠.

long id = db.insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_IGNORE);

데이터 중복이 없다면 id에는 입력한 데이터의 행 번호(row ID)가 들어갈 것입니다.
그런데, 만약 데이터 중복이 있다면 위의 작업은 실패하므로 정상적인 행 번호가 아닌 다른 값을 반환해야 합니다. 하지만, 실제 반환값을 확인해 보면

  • Android 3.0 미만 (API Level < 11) : 0
  • Android 3.0 이상 (API Level >= 11) : -1

이 되는 것을 확인할 수 있습니다. 

테스트한 테이블은 TEXT 형태의 Primary key를 가지는 테이블이였으므로, API 문서에서 설명한 대로 CONFLICT_IGNORE 인자를 사용했음에도 불구하고 충돌이 발생한 행의 Primary Key를 정상적으로 반환할 수 없습니다. 게다가 버전에 따라 반환값까지 달라져 버리니 난감하기만 합니다.

그러므로, 위의 메서드를 직접 사용해서 반환값으로 오류 여부를 확인하기 보다는, 아래 메서드를 사용하기를 권장합니다.
try {
    db.insertOrThrow(table, null, values);
} catch (SQLException e) {
    // Handle for data conflict
}