개인적으로 노래방 검색 앱을 만들어 쓰고 있는데 데이터가 몇만 개가 되다보니 아무리 서버에서 JSON 형식으로 빠르게 준다고 해도 그걸 가지고 로컬에 DB를 구축하는 게 꽤나 오래 걸렸다. 그래서 기본 DB를 내장한 채 출시하고 업데이트만 새로 받도록 하면 어떨까 했다.
방법은 꽤나 간단한데 asset(혹은 res.raw)로 두고 DB를 열기 전 데이터베이스 디렉터리 안으로 복사를 해 주면 된다. dbHelper.get(Readable|Writable)Database()
를 호출하기 전에는 DB를 열지 않으므로 db helper의 onCreate
에서 미리 DB 파일이 존재하는지 확인한 후 없으면 파일을 통채로 복사해 주면 된다.
public DbHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
this.context = context;
if (checkDbExists() == false) {
try {
copyDatabase(DB_NAME);
Log.i("DB", "Copy initial database succeeded.");
} catch (IOException e) {
Log.e("DB", "Failed to copy database");
}
}
}
private void copyDatabase(String filename) throws IOException {
InputStream input = context.getAssets().open(DB_NAME);
context.getDatabasePath(filename).getParentFile().mkdirs();
String dbPath = context.getDatabasePath(filename).toString();
OutputStream output;
output = new FileOutputStream(dbPath);
byte[] buffer = new byte[1024];
int length;
while ((length = input.read(buffer)) > 0) {
output.write(buffer, 0, length);
}
output.flush();
output.close();
input.close();
}
또 다른 문제가 하나 발생했는데 간편검색을 위한 컬럼을 추가하면서 초기 DB파일이 많이 바뀌었다. 신규 사용자는 역시 복사가 되겠지만 기존 사용자들은 DB가 이미 있기 때문에 파일을 복사하지 않을 것이고 몇만 개의 데이터에 대해 무거운 작업을 처리해야 했다. 다른 DB 파일에서 테이블 하나만 복사를 할 수 없나 싶다가 방법을 찾았다. attach 명령으로 다른 파일을 불러온 후 insert ~ select ~ from ~
으로 데이터를 복사하면 금방 끝났다.
단, 그냥 insert를 하면 중복된 행이 발생하니 unique (col1, col2) on conflict update
와 같은 스키마를 입혀 준 상태로 해야 한다.
여기서도 문제가 있는데 onUpgrade
메소드는 트랜잭션이 걸린 상태의 db를 인자로 전달해 준다. 하지만 attach
명령은 트랜잭션 중간엔 쓰지 못한다고 에러가 뜨는데 그래서 트랜잭션을 임시로 끝낸 후 작업을 한 뒤 다시 트랜잭션을 시작해 줘야 한다(사실 이 방법도 좋은 방법은 아니다. 트랜잭션이 왜 있는가).
db.execSQL(
"alter table " + TABLE_SONG + " add column " + COL_SIMPLIFIED +
" not null default \"\";"
);
try {
// Temporarily end transaction to attach database;
db.setTransactionSuccessful();
db.endTransaction();
String dbName = "karaoke_orig";
copyDatabase(dbName);
db.execSQL(MessageFormat.format("attach \"{0}\" as orig",
context.getDatabasePath(dbName).toString()));
db.execSQL("insert into songs select * from orig.songs");
db.execSQL("detach orig");
Log.d("MIGRATE", "Copy simplified from asset");
} catch (IOException e) {
e.printStackTrace();
} finally {
db.beginTransaction();
}