Friday, October 9, 2015

[Android Databass] SQLiteOpenHelper, ContentProvider, OrmLite, AndroidAnnotations @OrmLite - 1

이 블로그는 4개의 Documentation 을 참고하여 작성되었습니다. [ SQLiteOpenHelper Docs, ContentProvider Docs, OrmLite Docs, AndroidAnnotations @EProvider ]

팀원 중 한명이 AndroidAnnotations 를 기반으로한 주소록 어플을 빠르게 만들고 있는데, 그러던 중 SQLiteOpenHelper 클래스를 사용하여 쓰는 것을 볼 수 있었다. 초기 개발시에는 해당 클래스를 기반으로 개발하고 ContentProvider를 쓰고 @EProvider를 써본다고 해서.. 갑자기 3가지의 차이점이 궁금하게 되었다. 그래서 Android Docs에 가서 해당 내용에 대해서 분석해보았다.

1. SQLiteOpenHelper

먼저 SQLiteOpenHelper를 설명하자면 클래스 이름 그대로 Android 에서 내장 되어 사용되는 SQLite를 사용하는데 도와주는 Helper역할을 하는 클래스이다. Database Creation, Version Management, 즉, 데이터베이스 생성과 버전관리를 담당하는 부분이다. 해당 Class를 상속받는 클래스를 생성하면 아래의 코드와 같이 onCreate(SQLiteDatbase)onUpgrade(SQLiteDabase, int int) 2개의 method 구현이 반드시 이뤄져야 하며 생성자 또한 존재해야한다.

package com.juranoaa.sqlite;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class BaseSQLiteOpenHelper extends SQLiteOpenHelper{

    public BaseSQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {

    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

1.1. Constructor(Context, String, SQLiteDabase.CursorFactory, int) 생성자

해당 생성자의 경우에 중요한 부분이 있습니다. 역할은 당연히 database에 대한 create, open, manage하기 위한 부부이지만 해당 되는 부분은 getWritableDatabase(), getReadableDatabase() method를 부르기 전까지 데이터베이스 자체는 생성되지 않는 다는 점입니다. 앞으로 나올 부분에서도 해당 부분에 대한 언급을 계속하게 되기때문에 "데이터베이스 자체가 생성되지 않는다"라는 것은 반드시 숙지하고 넘어가시기 바랍니다.

1.2. onCreate(SQLiteDatabase), onUpgrede(SQLiteDatabae)

method 명과 같이 데이터 베이스를 생성하거나, 데이터베이스를 업그레이드 하는 역할을 하는 method입니다. 만약 Sqlite로 id, title, content를 가진 게사판을 구현한다고 하면 다음과 같이 작성됩니다.

public class Constant {

    public static final class SQLite {
        public static final Integer DB_VERSION = 1;
        public static final String DB_NAME = "AATest.db";

        public static final String TABLE_NAME = "board";
        public static final String DB_CREATE =  "CREATE TABLE " + TABLE_NAME + " ("+
                                                " id INTEGER PRIMARY KEY AUTOINCREMENT "+
                                                " title VARCHAR NOT NULL"+
                                                " content VARCHAR NOT NULL"+
                                                ")";
        public static final String DB_DROP =    "DROP TABLE IF EXIST " + TABLE_NAME ;
    }
}
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class BaseSQLiteOpenHelper extends SQLiteOpenHelper{

    public BaseSQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(Constant.SQLite.DB_CREATE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL(Constant.SQLite.DB_DROP);
        onCreate(db);
    }
}

1.3. getReadableDatabase(), getWritableDatabase()

두 개의 method는 특징이 있습니다. SQLiteOpenHelper의 경우 onCreate, onOpen, onUpgrade method 들이 생성자 시점에 불리는 것이 아니라 해당 method들이 호출될때 불리게 됩니다. 이렇게 구현이 되는 이유는 생성 또는 업데이트 과정에서 시간이 올래걸리게 된다면 Application이 호출하는데 늦게 활성화가 되기 때문입니다. 따라서 해당 method가 Application main thread에서 불리게 하는것은 되도록 지양해야합니다.

2. ContentProvider

ContentProvider는 AndroidApplication에서 application에서 제공하는 가장 중요한 부분 중 하나 입니다.Data는 Encapsulation(은닉화)되어있으며, ContentResolver를 이용하여 인터페이스간 통신을 제공해줍니다. 따라서 ContentProvider는 Application간에 데이터를 주고 받고자할 때 필요한 부분입니다. 만약에 Application간에 Data share가 발생하지 않는다면 굳이 contentProvider를 사용하는 것이아닌 SQLiteDatabase를 직접적으로 사용하면 됩니다.

(이 부분이 Docs에 쓰여져있긴 하지만 굳이 Database에 Directly 접근할 필요가 있을까? 라는 의문이 드네요).

그리고 아래와 같이 ContentProvider를 사용하게 되면 6개의 method가 반드시 작성되어 있어야만합니다.

public class BaseContentProvider extends ContentProvider{
    @Override
    public boolean onCreate() {
        return false;
    }

    @Nullable
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        return null;
    }

    @Nullable
    @Override
    public String getType(Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        return null;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return 0;
    }
}

2.1. Constructor() 생성자

저는 이 생성자가 가장 중요한 부분이라고 생각합니다. ContentProvider는 Application 실행시점에 application main thread를 사용하여 실행합니다. 따라서 아까 위에서 말했듯이 SQLiteOpenHelper를 통해 구현한 클래스에 대한 실행이 생성자 부분에서 실행되도록 하면 안됩니다. 해당 부분이 실행되게 되면 application이 늦게 시작이 될 수 있기 때문에 이부분이 가장 중요한 부분이라고 생각합니다.

2.2. onCreate()

Docs에 존재하는 이 method에 대한 설명 또한 시간이 오래걸리는 작업, Application 시작시 느려지는 것은 수행하지 않도록 해야 한다고 권장하고 있습니다. 따라서 SQLiteOpenHelper를 이용해서 Database의 생성부분을 관리하도록 짜는 것이 중요합니다. 또한 onCreate() method 내부에서 getReadableDatabase()getWriteabaleDatabase() method를 부른다면 Database 생성을 main thread에서 수행하는것과 마찬가지가 되기 때문에 이부분을 가장 중요시하게 다뤄야 합니다.

ContentProvider를 작성하면 다음과 같다.

Constant.class

public class Constant {

    public static final class SQLite {
        public static final Integer DB_VERSION = 1;
        public static final String DB_NAME = "AATest.db";
    }
}

Board.class

public class Board {

    private Integer id;

    private String title;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Board() {}

    public Board(Integer id, String title) {
        this.id = id;
        this.title = title;
    }

    @Override
    public String toString() {
        return "Board{" +
                "id=" + id +
                ", title='" + title + '\'' +
                '}';
    }
}

BoardProvider.class

public class BoardProvider extends ContentProvider {
    private static final String TAG = BoardProvider.class.getSimpleName();

    public static final String TABLE_NAME_BOARD = "dbBoard";
    private static final String AUTHORITY = "com.juranoaa.sqlite";

    public static final Uri CONTENT_URI_BOARD = Uri.parse("content://" + AUTHORITY + "/" + TABLE_NAME_BOARD);

    public static final String COL_ID = "_id";
    public static final String COL_TITLE = "title";

    /** create db string */
    public static final String DATABASE_CREATE_BOARD =
            "CREATE TABLE " + TABLE_NAME_BOARD +
            " (" + COL_ID + " INTEGER PRIMARY KEY,"
            + COL_TITLE + " TEXT );";

    private static BoardDataBaseHelper mDbHelperBoard;
    private SQLiteDatabase mDbBoard;

    @Override
    public boolean onCreate() {
        mDbHelperBoard = new BoardDataBaseHelper(getContext());
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        mDbBoard = mDbHelperBoard.getReadableDatabase();
        Cursor cursor = mDbBoard.query(TABLE_NAME_BOARD, projection, selection,
                selectionArgs, null, null, sortOrder);
        return cursor;
    }

    @Override
    public String getType(Uri uri) {
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        mDbBoard = mDbHelperBoard.getWritableDatabase();
        mDbBoard.insertOrThrow(TABLE_NAME_BOARD, null, values);
        return null;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        mDbBoard = mDbHelperBoard.getWritableDatabase();
        int count = mDbBoard.delete(TABLE_NAME_BOARD, selection, selectionArgs);
        return count;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        mDbBoard = mDbHelperBoard.getWritableDatabase();
        int count = mDbBoard.update(TABLE_NAME_BOARD, values, selection, selectionArgs);
        return count;
    }

    private static class BoardDataBaseHelper extends SQLiteOpenHelper {

        private static final String TAG = BoardDataBaseHelper.class.getSimpleName();

        public BoardDataBaseHelper(Context context){
            super(context, Constant.SQLite.DB_NAME, null, Constant.SQLite.DB_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL(DATABASE_CREATE_BOARD);

        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            Log.w(TAG, "invoked! " + oldVersion + " -> " + newVersion + ", it'll destroy old data");
            db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME_BOARD);
            onCreate(db);

        }
    }
}

BoardDao.class

public class BoardDao {

    public static final String COLS_BOARD_ARR[] = {
            BoardProvider.COL_ID,
            BoardProvider.COL_TITLE
    };

    private BoardDao() {} 

    /**
     * Insert new Board to DB.
     */
    public static void insertNewBoard(Context ctx, Board board) {
        ContentValues Values = new ContentValues();
        ContentResolver cr = ctx.getContentResolver();

        Values.put(BoardProvider.COL_TITLE, board.getTitle());

        cr.insert(BoardProvider.CONTENT_URI_BOARD, Values);
    }

    public static List getAllBoards(Context ctx) {
        List boards = new ArrayList();

        ContentResolver cr = ctx.getContentResolver();
        Cursor c = cr.query(BoardProvider.CONTENT_URI_BOARD,
                COLS_BOARD_ARR, null, null, null);

        if (c != null && c.getCount() > 0) {
            c.moveToFirst();
            while (c.isAfterLast() == false) {
                boards.add(new Board(
                        c.getInt(c.getColumnIndex(BoardProvider.COL_ID)),
                        c.getString(c.getColumnIndex(BoardProvider.COL_TITLE))));
                //LogUtil.w("map.put(" + uid + "," + persona_name+ ")");
                c.moveToNext();
            }
        }
        c.close();

        return boards;
    }

    public static Board getBoardById(Context ctx, int id) {
        Board board = null;

        ContentResolver cr = ctx.getContentResolver();
        Cursor c = cr.query(BoardProvider.CONTENT_URI_BOARD,
                COLS_BOARD_ARR, BoardProvider.COL_ID + "=?",
                new String[]{String.valueOf(id)}, null);
        if (c != null && c.getCount() > 0) {
            c.moveToFirst();
            while (c.isAfterLast() == false) {
                board = new Board(
                        c.getInt(c.getColumnIndex(BoardProvider.COL_ID)),
                        c.getString(c.getColumnIndex(BoardProvider.COL_TITLE)));
                c.moveToNext();
            }
        }
        c.close();

        return board;
    }
}

AndroidManifest.xml




    

        
        
    


Github 링크는 다음과 같습니다. https://github.com/JuranoSaurus/AndroidAnnotationsSampleProject/tree/sqlite.1.0

-------------------------------------------

다음 포스팅에서 OrmLite와 AndroidAnntation EProvide를 이용한 DB 구성에 대한 포스팅을 하도록 하겠습니다. 끝...

참고 논문 및 사이트

1. [Android/안드로이드] Content Provider ( 콘텐트 프로바이더 ) 에 대한 모든 것. http://aroundck.tistory.com/236

No comments:

Post a Comment