제임스딘딘의
Tech & Life

개발자의 기록 노트/Android

[안드로이드] 안드로이드에서 SQLite를 다뤄보자 (2)

제임스-딘딘 2011. 8. 9. 05:11



안드로이드에서 SQLite를 다뤄보자 (2)

안드로이드 앱 개발을 할 때, 없어서는 안될 존재. SQLite의 사용법에 대해 알아보겠다오늘은 아주 데이터베이스를 뿌리뽑아보도록 하자.

안드로이드는 모바일 환경에 알맞은 SQLite 데이터베이스를 채택하고 있다. 기본적으로 다른 데이터베이스와의 큰 차이는 없다. 물론, 완전히 같지는 않다.

다른 점 이라면, 일반적은 데이터베이스는 테이블 생성 시 각 속성에 대한 타입을 지정한다. 하지만 SQLite는 타입을 지정하는 것이 없다. 즉, int, string, text 등의 타입을 지정할 수가 없다는 말이다. 그리고 메모리와 속도면에서 소규모의 데이터베이스를 운영하는 데 있어서는 이점이 있다.

데이터베이스의 사용법은 기존의 데이터베이스, mySQL이나 Oracle 등 SQL을 사용하는 데이터베이스를 한번이라도 다루어 본적이 있다면  별다른 어려움 없이 사용할 수 있을 것이다. 처음 접하는 사람들 역시, 기존에 있는 샘플코드를 이용하여 조금만 수정해서 사용한다면, 별다른 어려움 없이 프로그램에 적용시키실 수 있을 것이다.

지금부터는 하나의 예제 코드를 가지고 구체적으로 안드로이드에서 SQLite를 사용하는 방법을 알아보겠다. 이 예제코드는 인터넷에 널리 퍼져 있는 코드를 정리한 것이다. 예제 코드를 따라가다보면 어느새 간단한 노트 기능의 앱을 만들게 될 것이며, 타이틀과 바디를 가지는 DB테이블, 그리고 이를 이용하여 데이터를 추가, 삭제, 업데이트 등을 수행할 수 있는 방법을 배우게 될 것이다.

   

Java Source Code 부분

 자바 코드는 크게 두가지로 나뉘어진다. 데이터베이스를 컨트롤 하는 객체와 이 객체를 사용하여 데이터베이스를 접근하는 엑티비티(Activity)이다.

 아래의 예제코드는 데이터베이스를 컨트롤 하는 객체, NotesDbAdapter, 이다. 이 클래스 내부에 DatabaseHelper라는 이름의 객체가 있고, 이 DatabaseHelper를 통해 데이터베이스를 관리한다. 이는 안드로이드에서 제공하는 SQLiteOpenHelper를 상속받아 간단히 만들 수 있다. DatabaseHelper 객체에는 크게 세 가지 메서드가 존재한다.

 생성자, onCreate, onUpdate 가 그것이다. 

이름 그대로 onCreate는 데이터베이스를 생성하는 시점에 호출되는 메서드로, 데이터베이스 이름과 버전 등을 설정할 수 있다. 이 메서드 안에서 쿼리문을 사용하여 데이터베이스의 테이블을 생성한다. 

onUpdate는 이름 그대로 업데이트가 필요할 시 수행이 된다. 현재의 데이터베이스 버전과 업데이트 하려는 데이터베이스의 버전을 비교하여, 낮은 버전일 경우 새롭게 테이블을 구성한다던가, 다른 조작 등을 취할 수 있다.


데이터베이스 관리 클래스

아래는 위에서 언급했던 데이터베이스를 컨트롤 하는 객체인 NotesDbAdapter의 Java코드이다.

  

import android.content.ContentValues; 
import android.content.Context; 
import android.database.Cursor; 
import android.database.SQLException; 
import android.database.sqlite.SQLiteDatabase; 
import android.database.sqlite.SQLiteOpenHelper; 
import android.util.Log; 

public class NotesDbAdapter {
    public static final String KEY_TITLE = "title"; 
    public static final String KEY_BODY = "body"; 
    public static final String KEY_ROWID = "_id"; 

    private static final String TAG = "NotesDbAdapter"; 
    private DatabaseHelper mDbHelper; 
    private SQLiteDatabase mDb;

    /**
    * Database creation sql statement
    */
    private static final String DATABASE_CREATE =
    "create table notes (_id integer primary key autoincrement, "
        + "title text not null, body text not null);";

    private static final String DATABASE_NAME = "data";
    private static final String DATABASE_TABLE = "notes";
    private static final int DATABASE_VERSION = 2;

    private final Context mCtx;

    private static class DatabaseHelper extends SQLiteOpenHelper {
        DatabaseHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

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

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 
            Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
              + newVersion + ", which will destroy all old data");
            db.execSQL("DROP TABLE IF EXISTS notes");
            onCreate(db);
        }
    }

    public NotesDbAdapter(Context ctx) {
        this.mCtx = ctx
    }

    public NotesDbAdapter open() throws SQLException {
        mDbHelper = new DatabaseHelper(mCtx);
        mDb = mDbHelper.getWritableDatabase();
        return this;
    }

    public void close() {
        mDbHelper.close();
    }

    public long createNote(String title, String body) {
        ContentValues initialValues = new ContentValues();
        initialValues.put(KEY_TITLE, title);
        initialValues.put(KEY_BODY, body);

        return mDb.insert(DATABASE_TABLE, null, initialValues);
    }

    public boolean deleteNote(long rowId) {
        Log.i("Delete called", "value__" + rowId);
        return mDb.delete(DATABASE_TABLE, KEY_ROWID + "=" + rowId, null) > 0;
    }

    public Cursor fetchAllNotes() {
        return mDb.query(DATABASE_TABLE, new String[] { KEY_ROWID, KEY_TITLE,
            KEY_BODY }, null, null, null, null, null);
    }

    public Cursor fetchNote(long rowId) throws SQLException {
        Cursor mCursor = mDb.query(true, DATABASE_TABLE, new String[] { KEY_ROWID, KEY_TITLE,
            KEY_BODY }, KEY_ROWID + "=" + rowId, null, null, null, null,
            null);
        if (mCursor != null) {
            mCursor.moveToFirst();
        }
        return mCursor;
    }

    public boolean updateNote(long rowId, String title, String body) {
        ContentValues args = new ContentValues();
        args.put(KEY_TITLE, title);
        args.put(KEY_BODY, body);
        return mDb.update(DATABASE_TABLE, args, KEY_ROWID + "=" + rowId, null) > 0;
    }
}


 그 밖에는 함수명에서도 볼 수 있듯이 데이터를 추가, 삭제, 업데이트하는 기능이 있다. 기존의 데이터베이스와 다른 점은 쿼리문을 이용하지 않고 데이터를 조작이 가능하다는 것이다. ContentValues 라는 type을 이용하여 기존에 있는 테이블의 속성명과 조작하려는 인스턴스를 넣어 한꺼번에 데이터베이스로 요청할 수 있다. Insert( ), Update( ) 와 같은 데이터베이스 객체 내에 있는 메서드를 이용하여 쿼리문 없이 데이터베이스 조작이 가능하다. 이러한 함수는 내부에서 직접 쿼리문을 만들어 데이터베이스를 조작한다.

자신이 사용하고 싶은 테이블을 구성한 뒤에, 이 코드를 자신에 맞는 테이블로 바꾸시면 큰 어려움 없이 데이터베이스를 이용하실 수 있을 것이다.

 

 Cursor의 메서드, 사용방법

moveToFirst

커서가 쿼리(질의) 결과 레코드들 중에서 가장 처음에 위치한 레코드를 가리키도록 한다.

moveToNext

다음 레코드로 커서를 이동한다.

moveToPrevious

이전 레코드로 커서를 이동한다.

getCount

질의 결과값(레코드)의 갯수를 반환한다.

getColumnIndexOrThrow

특정 필드의 인덱스값을 반환하며, 필드가 존재하지 않을경우 예외를 발생시킨다

getColumnName

특정 인덱스값에 해당하는 필드 이름을 반환한다.

getColumnNames

필드 이름들을 String 배열 형태로 반환한다.

moveToPosition

커서를 특정 레코드로 이동시킨다.

getPosition

커서가 현재 가리키고 있는 위치를 반환한다.

 

안드로이드 SQLite에서는 테이블에서 하나의 '레코드'를 읽어 오기 위해서 커서(cursor)라는 것이 필요하다. 조건에 맞는 레코드를 한꺼번에 모두 들고 올 수가 없기 때문에 커서를 조작해서 각각의 레코드에 접근 한다. 

커서라는 것을 이해할 때, 이름 그대로 '현재 레코드를 가리키고 있는 곳'이라고 생각하면 쉽다. 마우스 커서가 한 지점을 가리키는 것 처럼 말이다. 이 커서를 조작하여 각 레코드 사이를 이동하면서 데이터를 접근한다. 그리고 이 커서 객체를 이용하여 get( ) 메서드를 호출하여 컬럼 번호에 상응하는 데이터를 가져올 수 있다.

 


데이터베이스를 사용하는 사용자 화면 - 액티비티 소스코드

위에서 만든 데이터베이스를 관리하는 클래스의 인스턴스를 만들고 사용하는, 액티비티 소스코드를 살펴보겠다.

이 코드가 앱의 화면을 구성한다.


   

import android.app.Activity;
import android.database.Cursor;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class DatabaseTestActivity extends Activity {
    private NotesDbAdapter dbAdapter;
    private static final String TAG = "NotesDbAdapter";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        Log.d(TAG, "DatabaseTest :: onCreate()");
        dbAdapter = new NotesDbAdapter(this);
        dbAdapter.open();

        Button bt = (Button)findViewById(R.id.inputButton);
        bt.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                dbAdapter.createNote("title", "body");
                TextView tv = (TextView)findViewById(R.id.textView1);
                tv.setText("데이터베이스에 넣었습니다.");
                TextView tv1 = (TextView)findViewById(R.id.textView2);
                tv1.setText("Title과 Body를 데이터베이스에 저장하였습니다.");
                Log.d(TAG, "First Button Click");
            }
        });

        Button bt1 = (Button)findViewById(R.id.outputButton);

        bt1.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                Cursor result =    dbAdapter.fetchAllNotes();
                result.moveToFirst();
                while(!result.isAfterLast()){
                  String title = result.getString(1);
                  String body = result.getString(2);
                  TextView tv = (TextView)findViewById(R.id.textView1);
                  tv.setText(title);
                  TextView tv1 = (TextView)findViewById(R.id.textView2); 
                  tv1.setText(body);
                  result.moveToNext();
                }
                Log.d(TAG, "Second Button Click");
                result.close();
            }
        });
    }
}

  위의 코드는 실제 엑티비티에서 데이터베이스를 이용하는 것을 나타내는 소스코드이다. 위에서 살펴본 NotesDbAdapter의 인스턴스를 생성한 후, open( ) 메서드를 호출 한 뒤 사용하면 된다. 사용하는 법은 너무나도 직관적이라 생략하겠다. while문 내에서 커서를 순회하며 테이블에 있는 모든 레코드를 가져와서 화면에 뿌려주는 것을 수행하는 코드이다.  

   

XML Code

XML 로 레이아웃을 잡을 때, 간단히 테스트할 수 있는 TextView를 두 개만 생성하고 있다.  

   

AndroidManifest.xml

 데이터베이스를 사용하기 위해서 추가해야 하는 Permission은 없다. 즉, AndroidManifest.xml 의 수정은 필요하지 않다.

   

 

마무리 - SQLite 데이터베이스 사용하기

데이터베이스를 사용하기 위해서는 안드로이드에서 제공하는 SQLiteOpenHelper를 이용하여 간단히 데이터베이스를 조작할 수 있다. 예제에서는 이 것을 상속받아 객체를 만들고 이 객체를 자신의 데이터베이스에 맞게 조작하도록 클래스(NotesDbAdapter)를 만들고, 엑티비티에서는 이 NotesDbAdapter 클래스의 인스턴스를 하나 만들고, 이를 통해 데이터베이스를 직접 사용할 수 있게 하였다. 간단한 예제 코드를 이용하여 자신의 데이터베이스 테이블에 맞게 수정하여 사용하면 간단히 데이터를 저장할 수 있는 환경을 만들 수 있다.