타이타늄에서 SQLite 사용시 주의사항!

엄청난 삽질을 했다. 무려 반나절?

타이타늄 버그인지 원래 그런건지 모르겠지만,..

DB 테이블을 만들때 숫자로 ID 값을 만드는 경우가 있다. 이때 ID의 데이터 타입으로 INTEGER나 TEXT 타입으로 지정할수있는데, INTEGER로 할 경우 MAX_INT 의 범위가 모호하다.

아마 시스템마다 다를 것으로 예상되는데, 정확히 범위가 얼마인지는 테스트 안해봤다.
여튼 ID 값은 유니크해야하므로 INTEGER로 지정할 경우 AUTOINCREMENT 를 지정해 쓸수도 있다.

하지만 부득히 시스템에서 유니크한 값을 결정해 넣을 경우엔 시스템에서 제공하는 getGuid() 같은 함수가 있다면 그걸 사용하고, 없는 경우엔 Date.getTime() 같은 시간함수를 이용해 유니크한 값을 얻을수도 있다.

그렇데 이렇게 숫자로 지정된 타임스템프를 가져올 경우 시스템마다 범위가 달라서 인식을 못할수도 있다는 사실!!.
뚜둥~! 타이타늄에선 인식을 못했다. -_- 제기랄…

해결법은 간단히 INTEGER 속성을 TEXT타입으로 변경하면 된다.
하지만  이 문제를 찾아내는데 8시간을 소비했다

아~ 억울해~ ㅜㅜ
자바스크립트처럼 정밀도 오류를 내주던가…  아무런 오류없이 그냥 안되면 어찌하리오..ㅜㅜ..
여튼 주의하자!!

SQLite로 만든 DB 자동으로 업데이트 하기

앱을 만들다보면 앱안에서 사용하던 데이터를 종종 바꿔야하는 경우가 있다. 이럴 경우 앱을 다시 배포하거나 데이터를 DB로 만들어서 DB를 바꿔줘야한다. 하지만 전자의 경우 앱을 재배포해야하는 문제가 있기 때문에 후자를 선호한다.

아이폰 앱중에 서울버스앱이 바로 이런 경우에 해당한다. 노선정보가 매번 바뀔때마다 앱을 새로 배포하지않고, 노선정보 DB만 업데이트해준다.  사실 서울버스앱 내부적으로 SQLite를 써서 바꿔치고 있는지는 모르겠지만.. 아마도 그렇치 않을까 조심히 추측해본다.

그럼, SQLite를 이용해 데이터베이스를 바꿔치는 방법에 대해 알아보자.

타이타늄에서 SQLite DB 인스톨하기

일단, 타이타늄에서 DB를 인스톨하는 방법은 간단하다.

 Ti.Database.install( String path, String dbName) API 문서 참고 

아이폰의 경우 아래 코드와 같이 호출하게 되면, 해당 DB가 /Library/Private Documents/에 .sql 확장자가 붙어서 저장된다.

code>> Ti.Database.install(“test.db”, “DB”);
save path>> /Applications/apple_app_id/Library/Private Documents/DB.sql

위와 같은 경로에 DB가 저장되는 이유는 Apple 정책이기 때문에 특별한 이유는 없다.
물론 이유는 있겠지만, 자세한 내막은 모르므로 일단 패스~

SQLite DB 자동 업그레이드 하기

먼저 자동 업그레이드 시나리오는 이렇다.

  1. DB 서버가 따로 있고, 웹에서 DB를 관리한다.
  2. 웹에서 제공하는 DB에 뭔가 수정사항이 생긴다.
  3. 앱에서 웹DB의 수정이 있는지 체크한다. (체크하는 방법은 두가지다. 서버에서 푸시하던지, 아니면 주기적으로 체크하는 방법이 있다. )
  4. 여튼 수정이 있다면 웹DB를 HTTP요청을 통해 다운로드 받는다
    Ti.Network.createHTTPClient() 를 이용한다.
  5. 그리고 다운로드 받은 DB파일을 인스톨한다!
    Ti.Database.install()을 이용한다.

그런데 문제가 있다.  바로 DB가 인스톨되는 저장소가 /Library/Private Documents/ 라는 사실이다. 현재 타이타늄(2.1.3GA)버전에서는 해당 폴더에 직접 액세스하는 방법을 제공하고있지 않기 때문에 인스톨한 DB를 지울 방법이 없다는 사실!! 뚜둥…OTL

DB를 지우지 않고, 인스톨하는 방법

인스톨한 DB를 지우지 못한다면, 아무리 install() 메소드를 호출해도 DB가 복사되지 않는다!
따라서 여기서 핵심은 기존 DB를 지우는데 있다!! 물론 지우지 않고 해결하는 방법도 있다.

먼저 지우지 않고 해결하는 방법은 다른 이름으로 DB를 만들고, 내부적으로 DB경로를 바꾸는 방법이다. 뭐 일단 이 방법도 DB가 자주 업데이트 되지 않는다면 쓸만한 방법이다. 하지만 DB가 자주 업데이트되면 내부 공간에 파일이 누적되는 문제가 있다. 그래서 언제가는 지워야한다!!

여튼 다른 이름으로 DB를 만들고, 내부적으로 DB경로를 바꿀때는 Ti.App.properties 를 사용하면 유용하다. 프로퍼티는 특별한 DB를 설정하지 않아도 마치 쿠키처럼 동작하기 때문에 처음 앱을 설치할때는 앱내부DB를 이용해 설치하고, 이후에 웹에서 DB를 다운로드 받게 되면 프로퍼터를 이용해 새로운 DB경로를 설정해두면 된다.

기존 DB를 지우고 새롭게 인스톨하는 방법 

그럼 다시 본론으로 돌아와서 어찌됐꺼나 중요한건 기존 DB를 지워야한다는 사실이고, 현재 타이타늄 API는 /Library/Private Documents/ 폴더에 접근하는 방법을 제공하고 있지 않다는 사실이다.

이 문제는 약간의  꼼수가 필요하다. 결국 파일 시스템에서 접근할때의 최종 경로가 /Library/폴더를 가르키게만 하면 되기 때문에 일단은 Ti.FireSystem.applicationDirectory를 이용해 경로를 가져오고, 이 경로에서 /Application 경로를 /Library로 바꿔주기만하면 쉽게 해결된다!!

자 그럼 고기낚는법은 알려드렸으니, 나머지는 여러분들의 몫~
사실은 귀찮아서..-_-;;;..

 

Blob 이미지를 SQLite에 저장하고 불러오기

Blob 객체란?

타이타늄 모바일에서 Blob 객체는 내부 데이터를 바이너리로 가지고 있는 객체를 의미한다.

The blob is an abstract data type that represents binary information, often obtained through HTTPClient or via files. It often is used to store text or the actual data of an image.

그리고 종종 이 객체를 그대로 데이터베이스에 저장하고 싶을때가 있다. 예를 들면 카메라로 찍은 이미지를 서버에 저장하지 않고, 그냥 앱안에 저장하고 싶은 경우가  있을수 있다. 이런경우엔, 보통 2가지를 생각해볼 수있다.

  1. SQLite 를 이용해 이미지를 앱 안에 저장한다.
  2. 찍은 이미지를 로컬 파일 시스템에 저장하고, Native Path를 DB에 저장한다.

여기서 다룰 내용은 바로 1번에 해당하는 내용이다. 그럼, 본론으로 들어가자!

Blob 이미지와 SQLite

타이타늄 모바일 커뮤니티 Q&A 게시판을 뒤져보면, Blob 객체를 SQLite DB에 저장하고 싶어하는 사람들이 많다는 사실을 알수있다. 하지만 그 어떤 누구도 뾰족한 해답을 제시해주지 못하고 있다.

SQLite에 쓰려면 직렬화 하세요!

문제의 핵심은 바로 SQLite DB에 있다. SQLite 가 지원하는 데이터 타입에 분명 BLOB 타입이 존재하지만, 자바스크립트에서 쓰는 그 Blob 객체가 그대로 저장된다는 의미는 아니다. 결국, DB에 데이터를 쓰려면 객체를 직렬화해야한다. 가장 간단한 방법은 객체를 문자열로 만들면 된다. 그럼, Ti.Blob 객체가 제공하는 toString() 메소드를 이용하면 되겠네? 라고 생각할수도 있지만, 결론은 안된다. 그럼 도대체 어쩌란 말인가???

바이너리 객체를 인코딩하세요!

결론은 base64로 인코딩했다가, 디코딩해서 쓰면된다.  코드로 써보면 다음과 같다.

// 저장할땐, 인코딩
exports.setImage = function(_rowId, _blob) {
    var mydb = Ti.Database.open(DATABASE_NAME);
    var sImg = Ti.Utils.base64encode(_blob);
    mydb.execute('UPDATE [table_name] SET blobImg="'+ sImg +'" WHERE  rowId="'+_rowId+'"');
    mydb.close();
};

// 사용할때, 디코딩
var db = require('db');
var encodedImg = db.getImage(rowId);
var blobImg = Ti.Utils.base64decode(encodedImg);

Summary

  1. save Blob image to SQLite

    var sImg = Ti.Utils.base64encode(_blob);

  2. use Blob image from SQLite

    var encodedImg = db.getImage(rowId); var blobImg = Ti.Utils.base64decode(encodedImg) ;

  3. but it is too slow in titanium, don’t use it!

덧,

몇가지 테스트해서 구현해봤더니 이미지를 DB에 때려 박는 무식한 방법은 쓰지 않는게 좋겠다. 이미지를 Base64로 인코딩하고 디코딩하는데 시간이 너무 오래 걸린다. 여튼 위 방법은 속도가 엄청 느리므로 비추한다.