자바 웹 프로그래밍을 하면서 ORM을 당연하게 사용해왔는데,

안드로이드에서 SQLite DB 프로그래밍을 하려니.. ORM 쓰던게 습관이 되어있어서인지

귀찮은 점이 한두가지가 아니네요.


먼저 ORM에 대해 간략하게 소개하자면 Object Relational Mapping으로 객체와 릴레이션(RDB의 테이블)간에 매핑을 해 주는 역할을 합니다. 쉽게 말하자면 Book 이라는 객체가 있다면 이 객체를 ORM을 통해 RDB에 넣거나(Insert), 가져오거나(Select), 지우거나(Delete), 고칠 수(Update)도 있고, 기본적인 CRUD 외에도 스키마를 다루거나 조인 연산등 RDB의 기능을 객체지향적으로 다룰 수 있도록 도와줍니다.



ORM

이미지 출처 : 4 Benefits of Object-Relational Mapping (ORM)


SQLite를 대상으로 하는 안드로이드 기반 ORM 라이브러리를 몇개 찾아보았습니다. 아래와 같은 것들이 있네요.

  1. ActiveAndroid(https://www.activeandroid.com/)
  2. SqliteORM(https://github.com/kremerk/SqliteORM/wiki)
  3. ORMLite(http://ormlite.com)
  4. Storm(https://code.google.com/p/storm-gen/)
  5. Green Dao(http://greendao-orm.com)
  6. Sugar ORM(https://github.com/satyan/sugar)


글 제목에서도 알 수 있지만 Sugar ORM이 간단하게 쓰기에는 쉽게 쓸 수 있도록 되어있네요. 모두 사용해 볼 수는 없어서 각 라이브러리들의 Getting Start에서 사용 방법을 눈으로만 살펴봤는데, DB 핸들링 관련 코드를 만들기 위한 Generater와 사용을 위한 라이브러리가 따로 있는가 하면, 빌드 패스에 이것 저것 추가하고 설정도 하는 것도 있고, 이래 저래 사용하기 복잡해서 좀 귀찮아도 그냥 Helper 구현해서 쓰고 말지 하는 생각이 드는 것들이 대부분이었네요. (사실 새로운 걸 익힌다는 것에 대한 귀찮음이 맞지만요 :D )


어쨌거나 Sugar ORM이 개중에서는 가장 쓰기 쉽도록 되어있네요.

간략하게 사용 순서를 풀어보면

1. 안드로이드 프로젝트 libs 폴더에 라이브러리를 추가한다.

2. 매니페스트에 Application 클래스를 설정한다(보통은 설정되어있지 않고 디폴트로 사용합니다.)

3. 매니페스트에 meta-data 태그를 이용하여 4개의 프로퍼티를 추가한다.

4. 쓴다.






코드로 써 봐야 아는 것에 구구절절 글을 늘어놓는 것 보다 백문이 불여일견 백견이 불여일행이라 하였으니 해보도록 하겠습니다. 개발 환경은 Windows 7, Eclipse Indigo + Android SDK Plug-in입니다.


가장 먼저 Sugar ORM GitHub 다운로드 페이지에서 라이브러리 파일을 다운로드 받습니다.

https://github.com/satyan/sugar/blob/master/dist/sugar-1.1.jar

(글을 쓰는 13.03.04 기준 7개월 전에 업데이트 되었네요)


그런 다음 이클립스를 켜고 새 안드로이드 애플리케이션 프로젝트를 생성해 줍니다.



프로젝트 이름이나 패키지는 본인의 입맛에 맞게 설정하시면 되겠습니다.

다만 Package Name은 기억해 두도록 하세요. 저는 nnoco.example.sugar 로 기본 패키지 경로를 정했습니다.

나중에 Manifest.xml 파일에 속성값으로 쓸 일이 있습니다.


Next -> Next -> ... Finish를 하면 프로젝트가 짠! 하고 만들어졌습니다.


ADK 플러그인 버전에 따라서 libs 폴더를 자동으로 생성해주기도 하고, 없는 경우도 있는데요, libs 폴더는 기본적으로 안드로이드 패키지에서 라이브러리가 있는 클래스패스로 인식을 합니다. 외부 라이브러리 파일의 경우 이곳에 복사하시면 클래스패스에 따로 추가하지 않으셔도 자동으로 잡히게 됩니다.

따라서 프로젝트 루트에 libs가 없는 분은 libs 폴더를 하나 추가해주시면 되겠습니다. 그럼 아래와 같은 프로젝트 구조가 보이게 됩니다. (환경에 따라 조금씩 다를 수 있지만 여기서는 libs 폴더만 있으면 됩니다.)



그럼 저 libs 폴더 안에 프로젝트를 만들기 전에 다운로드 받은 sugar-1.1.jar 파일을 복사해서 넣어줍니다.




여기까지 되었다면 이제 Manifest.xml 파일을 편집해 보겠습니다.

해야할 것은 Custom Appication 클래스를 지정하는 것과, meta-data의 추가입니다.


<manifest xmlns:android="http://schemas.android.com/apk/res/android"

    package="nnoco.example.sugar"

    android:versionCode="1"

    android:versionName="1.0" >


    <uses-sdk

        android:minSdkVersion="8"

        android:targetSdkVersion="15" />


<!-- Custom Application 지정 -->

    <application

        android:name="com.orm.SugarApp"

        android:icon="@drawable/ic_launcher"

        android:label="@string/app_name"

        android:theme="@style/AppTheme" >

        <activity

            android:name=".MainActivity"

            android:label="@string/title_activity_main" >

            <intent-filter>

                <action android:name="android.intent.action.MAIN" />


                <category android:name="android.intent.category.LAUNCHER" />

            </intent-filter>

        </activity>

        <!-- 메타 데이터 추가 -->

        <meta-data android:name="DATABASE" android:value="sugar_example.db"/>

        <meta-data android:name="VERSION" android:value="2"/>

        <meta-data android:name="QUERY_LOG" android:value="true"/>

        <meta-data android:name="DOMAIN_PACKAGE_NAME" 

                      android:value="nnoco.example.sugar"/> <!-- 본인의 패키지로 설정-->

    </application>


</manifest>



저렇게 두 부분만 (라인으로는 5줄!) 추가하면 사용할 준비는 끝입니다.

이제 사용 해보도록 하겠습니다. 만들고자 하는 엔티티는 다음과 같습니다.



간단합니다. 책 엔티티로 제목(title)과 저자(author)를 가지고 있습니다.

여기에 맞는 모델 클래스를 만들어야겠죠? 원하는 위치에 Book 클래스를 하나 만들어보도록 하겠습니다.

모델 클래스를 만들 때 Sugar ORM을 사용하면 하나의 제약사항이 있습니다. SugarRecord를 상속해서 구현해야 한다는 점과, Context를 매개변수로 받는 생성자가 필요합니다. 사실 SugarRecord를 상속 받는 건 제약이라기 보다 편의쪽에 가깝다고 봐야겠네요.




public class Book extends SugarRecord {

private String title;

private String author;


public Book(Context context) {

super(context);

}


public Book(Context context, String title, String author) {

super(context);

this.title = title;

this.author = author;

}


public String getTitle() {

return title;

}


public void setTitle(String title) {

this.title = title;

}


public String getAuthor() {

return author;

}


public void setAuthor(String author) {

this.author = author;

}

}


Context 객체를 받는 생성자와 title, author를 포함해서 받는 생성자, getter/setter를 만들어준 모습입니다.



여기까지 되었으면 메인 액티비티로 이동해서 onCreate 메서드에서 간단하게 테스트를 해보도록 하겠습니다.

// Book 인스턴스 생성

Book book = new Book(this, "논어", "공자");

        

// DB에 저장

book.save();

        

// 수정

book.setTitle("論語");

book.save();

        

// DB로부터 ID 가져오기

long id = book.getId();

        

// ID를 이용해서 객체 가져오기

Book foundBook = Book.findById(Book.class, id);

        

// 로그로 확인

Log.d("Found Book Info", foundBook.getTitle() + ", " + foundBook.getAuthor());

        

// 삭제

book.delete();


api는 참 쉽죠? save()는 없다면 생성하고, 있다면 수정합니다. delete()는 삭제구요.

static 메서드에는 find(...), 나 findById(...)과 같이 select의 기능을 하는 메서드가 있고,

listAll(...) 테이블의 모든 레코드를 가져와 리스트로 반환하는 메서드,

deleteAll(...) 과 같이 테이블의 모들 레코드를 삭제하는 메서드가 있습니다.


이외에도 1:1 관계의 표현, 1:N관계의 표현등에 대한 내용이 있지만 간단하게 소개하는 글이므로 여기까지 쓰도록 하고

더 자세한 내용은 Sugar ORM의 위키를 통해 확인하실 수 있습니다.(http://satyan.github.com/sugar/getting-started.html)


레이아웃을 어느정도 만들어서 EditText 뷰에 속성값을 넣고 추가하고 리스트뷰에서 보여주고 하는 식으로 하려다가 단순한 기능 때문에 오히려 다른 부분(리스트뷰 어댑터 만들고, 이벤트 추가하는 등의)의 작업이 많아지면 의미없는 일이라고 생각하여 그 부분은 제외하였습니다. 

리스트 뷰를 사용하여 DB내용을 확인하는 예제는 첨부하여 올리니 확인하실 분들은 다운로드 받으셔서 확인하시면 됩니다.


Android ORM Test.zip





  1. 에코지오 2013.06.18 11:06 신고

    안녕하세요. Sugar ORM에 좀 심각한 버그가 있어 알려드립니다.
    보통은 DB처리 작업은 백그라운드에서 처리를 하는게 맞죠. 올려주신 소스중 MainActivity의 fillFromDB() 메소드를 백그라운드에서 처리되도록 아래와 같이 수정해서 테스트해보시기 바랍니다.

    private void fillFromDB() {
    new AsyncTask<Void, Void, Void>() {
    @Override
    protected Void doInBackground(Void... params) {
    books.addAll( Book.listAll(Book.class));
    return null;
    }

    protected void onPostExecute(Void result) {
    adapter.notifyDataSetChanged();
    }
    }.execute();
    }

    AsyncTask를 사용하는 경우 Sugar ORM이 이렇게 에러를 토해냅니다.

    FATAL EXCEPTION: AsyncTask #1
    java.lang.RuntimeException: An error occured while executing doInBackground()
    at android.os.AsyncTask$3.done(AsyncTask.java:278)
    at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:273)
    at java.util.concurrent.FutureTask.setException(FutureTask.java:124)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:307)
    at java.util.concurrent.FutureTask.run(FutureTask.java:137)
    at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:208)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
    at java.lang.Thread.run(Thread.java:856)
    Caused by: java.lang.ExceptionInInitializerError
    at java.lang.Class.classForName(Native Method)
    at java.lang.Class.forName(Class.java:217)
    at com.orm.SugarDb.getDomainClass(Unknown Source)
    at com.orm.SugarDb.getDomainClasses(Unknown Source)
    at com.orm.SugarDb.createDatabase(Unknown Source)
    at com.orm.SugarDb.onCreate(Unknown Source)
    at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:165)
    at com.orm.Database.openDB(Unknown Source)
    at com.orm.SugarRecord.find(Unknown Source)
    at com.orm.SugarRecord.listAll(Unknown Source)
    at nnoco.android.orm.test.MainActivity$1.doInBackground(MainActivity.java:44)
    at nnoco.android.orm.test.MainActivity$1.doInBackground(MainActivity.java:1)
    at android.os.AsyncTask$2.call(AsyncTask.java:264)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
    ... 5 more
    Caused by: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
    at android.os.Handler.<init>(Handler.java:121)
    at android.support.v4.content.ModernAsyncTask$InternalHandler.<init>(ModernAsyncTask.java:466)
    at android.support.v4.content.ModernAsyncTask$InternalHandler.<init>(ModernAsyncTask.java:466)
    at android.support.v4.content.ModernAsyncTask.<clinit>(ModernAsyncTask.java:75)
    ... 19 more


    Sugar ORM개발자가 context와 클래스로더에 대해서 고민이 부족해서 발생한 문제로 보입니다.

    • 마음이 뛰다 2013.06.19 06:05 신고

      소스를 살펴보려고 GitHub에 갔더니 어제 날짜로 1.2 버전으로 업데이트 되었네요.
      상세한 변경사항은 알 수 없으나 말씀하신 내용은 수정된 듯 합니다.

+ Recent posts