2016년 07월 26일 | Steve.Jung

Android 의 Sqlite Tip

Android 와 Sqlite

Android 에서 Sqlite 는 오랫동안 사용되어 왔습니다. 지금은 Realm 과 그외의 데이터베이스들이 그 위치를 넘보고 있지만 여전히 사용되고 있음은 틀림없습니다.

현재 Jandi 는 서버의 대다수 정보를 앱의 Sqlite-Database 에 Cache 를 하고 있습니다. 따라서 Sqlite 를 얼마나 잘 분리하고 제어하느냐가 앱 자체의 라이프사이클에 큰 영향을 미칩니다.

오늘은 Android 팀이 Sqlite 를 어떻게 사용하는지 공유해드리고자 합니다.

1. ORM

안드로이드만 하신 분들에게는 다소 생소한 개념일 수 있지만 다른 분야에서는 널리 사용되고 있습니다.

Android 에서 Sqlite 는 Database 용 Access 객체를 통해서 column/row 단위로 정보를 가져와서 객체를 완성합니다. 하지만 Access 객체에 일일이 Query 를 작성하는 것은 실수가 많을 뿐더러 column/row 단위 정보 매핑 작업은 매우 불편하고 지루하며 잠재적 버그를 내포한 작업니다.

그러기 때문에 Object-Query-Databse 를 각각에 맞게 매핑해주는 라이브러리들 통해 단순하고 반복적인 작업을 간단하게 회피할 수 있습니다.

아래의 블로그들이 Sqlite-Orm 라이브러리를 사용하는 좋은 정보들이 될 것입니다. 현재 Jandi-Android 의 주요 Orm 라이브러리는 OrmLite 입니다.

Sqlite-Orm : 네이버 기술블로그

GreenDao Benchmark

Realm Database

2. Database-Access

유사 관심사 Domain 끼리 묶음

수많은 데이터를 테이블로 관리하다보면 많은 Database-Access-Object(DAO) 가 필요합니다. 하지만 자세히 들여다보면 관계된 것끼리의 묶음이 생기게 되며 이를 묶어서 하나의 Access 객체를 만들 수 있습니다.

Jandi 의 메시지는 크게 Text, File, Sticker 로 구분되어 있으며 이에 대한 상위로 Message 라는 개념이 있습니다. Text, File, Sticker 는 하위에 각각 2~3개의 Table 로 구성되어 있습니다. 이 전체를 각각 분리해서 관리하면 그에 따른 부수적인 제어 코드들이 불가피 하기 때문에 Jandi 에서는 최상위 Message 도메인에 맞춰서 하나의 묶음으로 관리하였습니다.

Message Domain

코드는 다음과 같은 형태를 띄고 있습니다.

public class MessageRepository {
  public List<Message> getMessages(/*args...*/) { /*코드 생략*/};
  public Message save(/*args...*/) { /*코드 생략*/};
  public Message update(/*args...*/) { /*코드 생략*/};
  public Text getText(/*args...*/) { /*코드 생략*/};
  /*이하 생략*/
}

이러한 형태로 독립성을 가질 수 있는 최상위 Domain 을 기준으로 Repository 클래스를 가지고 있습니다.

3. Repository 요청 관리하기

위의 모습처럼 관심사별로 Domain 을 분리한 이유 중 가장 큰 이유는 Domain 단위로 요청을 관리하기 위함입니다.

Android-Sqlite 는 내부적으로 Read-Write lock 을 가지고 있지만 신뢰도가 높다 할 수 없으며 다양한 테이블에 동시 접근하는 경우 오류가 나지 않을 것이라 보장할 수 없습니다.

따라서 보장이 안될바에 1번에 1개의 요청만 처리 할 수 있도록 Domain 단위로 요청을 제한해버리자는 결론을 냈습니다.

그러기 위해 2가지 코드를 사용하였습니다.

  1. Lock 객체 사용
  2. 요청을 래핑할 template interface 사용하기

멀티 쓰레드로 요청을 처리할 때 Lock 객체를 통해 1번의 1개씩의 동작만 할 수 있도록 하였으며 이를 좀 더 쉽게 쓸 수 있도록 하기 위해 Template Interface 를 만들었습니다.

public class LockTemplate {
  private Lock executorLock;
  LockTemplate() {
    executorLock = new ReentranceLock();
  }
  protected <T> T execute(Executable<T> e) {
    executorLock.lock();
    try {
      return e.execute();
    } finally {
      executorLock.unlock();
    }
  }

  interface Executable {
    <T> T execute();
  }
}

위와 같은 클래스를 만들고 앞서 만든 Repository 클래스에 상속받도록 하였습니다.

LockTemplate

코드는 다음과 같습니다.

public class MessageRepository extends LockTemplate {
  /*싱글톤으로 동작하도록 합니다. 코드 생략*/
  public List<Message> getMessages(long roomId) {
    return execute(() -> {
      return dao.query(roomId);
    });
  }

  public int save(List<Message> messages) {
    return execute(() -> {
      return dao.save(messages);
    });
  }
}

위와 같이 함으로써 최종적으로 같은 Repository 에 멀티쓰레드에서 요청을 하여도 1개의 처리만 할 수 있도록 원천적으로 작업하였습니다.

정리

Android 에서 Sqlite 는 Mysql 이나 PostSQL 과 유사한 RDBMS 를 제공하는 DB 툴입니다. 하지만 기본적인 사용이 매우 번거러울 뿐만 아니라 메모리릭과 오류에 매우 쉽게 노출됩니다. 그래서 다음과 같은 방법을 통해 최소한의 안전망을 구현했습니다.

  1. ORM 을 사용하라.
    • 반복적이고 DB 접근 과정에서 오류를 최소화 시켜줍니다.
  2. Lock 을 의도적으로 사용하라.
    • 멀티쓰레드 접근에 의한 오류를 최소화 합니다.
    • synchroized 보다는 concurrent 패키지에서 제공해주는 Lock 을 사용해주세요.