태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

데이터베이스 이용하기 - (3) 데이터베이스 어댑터 만들기

2009.08.01 02:54


데이터베이스 강좌로는 꽤 오래간만에 찾아뵙는군요.
제가 요즘 병행하고 있는 일이 한두개가 아닌데다가, 강좌를 자유롭게(?) 쓸 여건은 되지 않다보니 계속 미뤄지기만 했네요.
아무튼, 이번 강좌에서는 실제로 데이터베이스 어댑터를 생성하여 어플리케이션에 적용하는 것에 대해 알아보겠습니다.

데이터베이스의 어댑터의 역할

"어댑터"에 대한 개념은 예전에 리스트를 설명할 때 처음 등장했습니다. (참고 : 2009/06/08 - [안드로이드 입문/GUI 구성하기] - #11. List 집중공략! - (3) Custom ArrayAdapter를 이용한 ListView)

리스트 어댑터는 실제 데이터와 그 데이터를 표시해주는 리스트뷰 사이에 위치하면서 데이터를 리스트에 표시해주는 역할을 합니다. 즉, 다른 체계를 가지고 있는 개체 사이에서 서로가 호환이 될 수 있도록 적절히 데이터의 형태를 변환해주는 것이 어댑터의 역할이라고 할 수 있죠.

그렇다면, 데이터베이스 어댑터의 역할은 무엇일까요? - 데이터베이스 어댑터 사실 어댑터의 기본 개념과는 약간 다른 개념을 가지고 있습니다. 서로 다른 두 개체를 호환시켜주는 역할이라기보다는, 데이터베이스에 접근하여 수행하는 작업들을 추상화시켜주는 역할을 합니다.

데이터베이스를 다루다보면 데이터베이스에 접근하여 데이터 추가, 수정, 삭제 등의 작업을 하게 되는데 이러한 작업의 내용은 겉으로 드러내지 않아야 데이터베이스 구조의 노출도 막을 수 있고, 쓸데없이 코드를 길게 쓰는 일도 줄어들게 되죠.

일반적인 데이터베이스 어댑터의 구조, 기능

일반적으로 데이터베이스 어댑터는 다음과 같은 구조를 가지고 있습니다.

  • 필드 이름들
  • 데이터베이스 초기화에 필요한 SQL 문장들
  • 데이터베이스 정보 (테이블 이름, 데이터베이스 이름 등)
  • 헬퍼 클래스 (SQLiteOpenHelper; 데이터베이스 열기/닫기를 담당)
  • 헬퍼 클래스의 인스턴스
  • 데이터베이스 (SQLiteDatabase)의 인스턴스
  • 데이터베이스 작업에 필요한 메소드들


  • 필드 이름들

    지난 강좌 (2009/07/04 - [안드로이드 입문/데이터 이용하기] - 데이터베이스 이용하기 - (2) 안드로이드 데이터베이스 기초) 에서 봤던 것처럼, 데이터베이스는 테이블과 테이블 안에 필드(열)과 레코드(행)으로 이루어져있습니다. 이 중, 필드 이름은 앞으로 어댑터 내에서 심심하면 쓰게 됩니다. 데이터베이스를 추가할 때도, 수정할 때도, 테이블을 생성할 때에도 필요하니... 보통 자주 쓰이는 것이 아니죠. 보통, 이렇게 자주 쓰이는 것들은 데이터베이서 어댑터 내에 static final String 형태, 즉 "상수"처럼 저장해놓고 사용합니다.

    예) 
    	public static final String KEY_NAME = "name";
    	public static final String KEY_PHONE = "phone";
    	public static final String KEY_ROWID = "_id";
    



  • 데이터베이스 초기화에 필요한 SQL 문장들

    데이터베이스를 처음 생성할 때 필요한 SQL문장들입니다. 일반적으로 테이블을 생성하는 문장이 들어갑니다.

    예)
    	private static final String DATABASE_CREATE =
    		"create table data (_id integer primary key autoincrement,"+
    		"name text not null, phone text not null);";
    


  • 데이터베이스 정보 (테이블 이름, 데이터베이스 이름 등)

    테이블 이름, 데이터베이스 이름, 데이터베이스 버전입니다. 데이터베이스 버전은 나중에 데이터베이스를 업데이트할 때 업데이트 여부를 결정하는 기준이 됩니다.


    	private static final String DATABASE_NAME = "datum.db";
    	private static final String DATABASE_TABLE = "data";
    	private static final int DATABASE_VERSION = 1;
    

  • 헬퍼 클래스 (SQLiteOpenHelper; 데이터베이스 열기/닫기를 담당)

    데이터베이스 파일을 열고 닫는 작업을 수행해주는 클래스입니다. 필요하다면 데이터베이스를 생성하고, 업그레이드합니다. 데이터베이스를 생성하는 메소드인 onCreate()와 데이터베이스의 업그레이드를 담당하는 onUpgrade() 메소드를 필수로 구현해야 합니다. (두 메소드 모두 추상 메소드 형태로 선언되어있음)


    	private class DatabaseHelper extends SQLiteOpenHelper{
    
    		public DatabaseHelper(Context context) {
    			super(context, DATABASE_NAME, null, DATABASE_VERSION);
    			// TODO Auto-generated constructor stub
    		}
    		
    		public void onCreate(SQLiteDatabase db){
    			db.execSQL(DATABASE_CREATE);
    		}
    		
    		public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
    			Log.w(TAG, "Upgrading db from version" + oldVersion + " to" +
    					newVersion + ", which will destroy all old data");
    			db.execSQL("DROP TABLE IF EXISTS data");
    			onCreate(db);
    		}
    		
    	}
    

  • 헬퍼 클래스의 인스턴스

    헬퍼 클래스는 데이터베이스를 열고 닫는 것을 도와주는 클래스라고 했었죠? 이런 역할을 하는 헬퍼 클래스를 이용하기 위해서는 헬퍼 클래스 형태를 가지는 인스턴스가 있어야겠지요?


    	private DatabaseHelper mDbHelper;
    

    이렇게 선언한 헬퍼 클래스의 인스턴스는 뒤에서 데이터베이스 어댑터 클래스 내의 데이터베이스를 여는 메소드, 닫는 메소드에서 사용되게 됩니다.

  • 데이터베이스(SQLiteDatabase)의 인스턴스

    실제 데이터베이스에 접근할 때 사용하는 인스턴스입니다. 이 인스턴스에서 insert(), update(), query(), delete()등의 메소드를 호출하여 원하는 작업을 수행하게 됩니다.

    	private SQLiteDatabase mDb;
    

  • 데이터베이스 작업에 필요한 메소드들

    실제로 데이터베이스를 가지고 수행할 작업들에 대한 메소드입니다. 일반적으로 데이터 추가, 수정, 삭제, 질의(Query) 작업을 수행하게 되므로, 이에 적합한 작업을 수행하는 메소드를 작성합니다. 아래는 예제입니다. 

    	public long createBook(String name, String phone){ // 레코드 생성
    		ContentValues initialValues = new ContentValues();
    		initialValues.put(KEY_NAME, name);
    		initialValues.put(KEY_PHONE, phone);
    		
    		return mDb.insert(DATABASE_TABLE, null, initialValues);
    	}
    
    	public boolean deleteBook(long rowID){ // 레코드 삭제
    		return mDb.delete(DATABASE_TABLE, KEY_ROWID + "=" + rowID, null) > 0;
    	}
    	
    	public Cursor fetchAllBooks(){ // 모든 레코드 반환
    		return mDb.query(DATABASE_TABLE, new String[]{KEY_ROWID, KEY_NAME, KEY_PHONE}, null, null, null, null, null);
    		
    	}
    	
    	public Cursor fetchBook(long rowID) throws SQLException{ // 특정 레코드 반환
    		Cursor mCursor =
    			mDb.query(true, DATABASE_TABLE, new String[]{KEY_ROWID, KEY_NAME, KEY_PHONE}, KEY_ROWID + "=" + rowID, null, null, null, null, null);
    		if(mCursor != null)
    			mCursor.moveToFirst();
    		return mCursor;
    	}
    	
    	public boolean updateBook(long rowID, String name, String phone){ // 레코드 수정
    		ContentValues args = new ContentValues();
    		args.put(KEY_NAME, name);
    		args.put(KEY_PHONE, phone);
    		
    		return mDb.update(DATABASE_TABLE, args, KEY_ROWID + "=" + rowID, null) > 0;
    	}
    


데이터베이스 어댑터 예제


아래는 데이터베이스 어댑터의 예제입니다. 저장하는 데이터는 String형 데이터 2개입니다.

package com.androidhuman.dbExample;

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 DbAdapter {
	
	public static final String KEY_NAME = "name";
	public static final String KEY_PHONE = "phone";
	public static final String KEY_ROWID = "_id";
	
	public static final int FIND_BY_NAME = 0;
	public static final int FIND_BY_PHONE = 1;
	
	private static final String TAG = "DbAdapter";
	private DatabaseHelper mDbHelper;
	private SQLiteDatabase mDb; // 데이터베이스를 저장
	
	private static final String DATABASE_CREATE =
		"create table data (_id integer primary key autoincrement,"+
		"name text not null, phone text not null);";
	
	private static final String DATABASE_NAME = "datum.db";
	private static final String DATABASE_TABLE = "data";
	private static final int DATABASE_VERSION = 1;
	
	private final Context mCtx;
	
	private class DatabaseHelper extends SQLiteOpenHelper{

		public DatabaseHelper(Context context) {
			super(context, DATABASE_NAME, null, DATABASE_VERSION);
			// TODO Auto-generated constructor stub
		}
		
		public void onCreate(SQLiteDatabase db){
			db.execSQL(DATABASE_CREATE);
		}
		
		public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
			Log.w(TAG, "Upgrading db from version" + oldVersion + " to" +
					newVersion + ", which will destroy all old data");
			db.execSQL("DROP TABLE IF EXISTS data");
			onCreate(db);
		}
		
	}
	
	public DbAdapter(Context ctx){
		this.mCtx = ctx;
	}
	
	public DbAdapter open() throws SQLException{
		mDbHelper = new DatabaseHelper(mCtx);
		mDb = mDbHelper.getWritableDatabase();
		return this;
	}
	
	public void close(){
		mDbHelper.close();
	}
	
	public long createBook(String name, String phone){
		ContentValues initialValues = new ContentValues();
		initialValues.put(KEY_NAME, name);
		initialValues.put(KEY_PHONE, phone);
		
		return mDb.insert(DATABASE_TABLE, null, initialValues);
	}

	public boolean deleteBook(long rowID){
		return mDb.delete(DATABASE_TABLE, KEY_ROWID + "=" + rowID, null) > 0;
	}
	
	public Cursor fetchAllBooks(){
		return mDb.query(DATABASE_TABLE, new String[]{KEY_ROWID, KEY_NAME, KEY_PHONE}, null, null, null, null, null);
		
	}
	
	public Cursor fetchBook(long rowID) throws SQLException{
		Cursor mCursor =
			mDb.query(true, DATABASE_TABLE, new String[]{KEY_ROWID, KEY_NAME, KEY_PHONE}, KEY_ROWID + "=" + rowID, null, null, null, null, null);
		if(mCursor != null)
			mCursor.moveToFirst();
		return mCursor;
	}
		
	public boolean updateBook(long rowID, String name, String phone){
		ContentValues args = new ContentValues();
		args.put(KEY_NAME, name);
		args.put(KEY_PHONE, phone);
		
		return mDb.update(DATABASE_TABLE, args, KEY_ROWID + "=" + rowID, null) > 0;
	}

	
}




저작자 표시 비영리 변경 금지
신고

커니 데이터 관리/SQLite3 , , , , , ,

  1. 이전 댓글 더보기
  2. Blog Icon
    일목

    일목요연한 설명보고 정말 많은 도움이 되었습니다
    그런데 한가지
    아래 구문들은 왜 넣는거죠?

    public DbAdapter(Context ctx){
    this.mCtx = ctx;
    }

    public DbAdapter open() throws SQLException{
    mDbHelper = new DatabaseHelper(mCtx);
    mDb = mDbHelper.getWritableDatabase(); //이 부분은 헬퍼를 통해 디비를 읽고쓰겠다는걸 알려주는 것이고..
    return this;
    }

  3. DatabaseHelper 생성자 부분을 보시면 Context의 인스턴스를 사용하는 걸 확인하실 수 있습니다 :)

  4. Blog Icon
    카이게츠

    커니님 수고하십니다. 눈팅만 하다가 처음 질문을 올려봅니다
    현제 어플 개발에 참여 중입니다만, 실 수행 코드에서는 에러가 발행하지 않으나...contentprovider관련 Instrumentest 시에 leak가 발생해서 조사중입니다. 직접 질문입니다만,
    저의 인식이라면
    db.open하고
    cursor.open();후에 필요한 처리수행후 커서객체에 담아서 어뎁터를 이용하여 결과를 화면에 표시하게 되는 인식입니다. 그후
    cursor.close();를 콜하여 커서를 닫아 주는 인식입니다.
    하지만.. 여기서 의문이 cursor.close();만 콜하여주는것으로 db.close를 수행 해 주지 않아도
    동작이 정상적으로 이루어지는 상태입니다. cursor.close();만으로도 디비연결이 자연스럽게 닫혀지는것인지
    아니면 제가 알지 못하는 부분의 처리가 Android제공 API속에 (OpenHelper)에 명시되어있는것인지 잘 모르겠습니다.
    현제 자신의 인식이라면 Instrumentest 시에 leak가 발생한 원인이 테스트 코드에서는 이 해당부분 (leak가 발생부분)의 처리가 db오픈후 아무것도 하지 않은체 열린상태로 정상적으로 닫지 않아서 발행한것이라는 추론이라서...
    제가 여쭙고 싶은것은 ( 디비 오픈후 커서 오픈 --> 처리 --> 커서 클로즈 ) 많으로도 실행시에는 문제가 없는것이 어떤 백단 처리가 수행 되어지는것인지(자신이 모르는) 그래서 이부분에 실 코드에서는 알지 못했지만.. 테스트 코드에서는 leak가 발생한것인지? 입니다.
    많이 바쁜 와중에 여쭙게 되어 죄송합니다. 항상 건강하시길 바라며, 저의 모자란 인식 부분이 있다면
    가르침 부탁 드립니다.

  5. 컨텐트 프로바이더를 사용할 때의 장점 중 하나가, 데이터베이스를 열고 닫는 동작이 자동으로 이루어진다는 것입니다.

    즉, 말씀하신 대로 컨텐트 프로바이더 자체에서 데이터베이스를 자동으로 열고 닫아주기 때문에, 개발자는 커스 객체만 잘 닫아주면 누수 현상을 방지할 수 있습니다. :)

  6. Blog Icon
    AKaZe

    커니님 강좌를 보고 기초적인 db이용과 listview이용방법을 배워갑니다
    제가 db table을 3-5개 사용하는 어플을 만드는중인데요
    안드로이드는 db를 파일처럼 관리하더라고요
    여러개의 table을 생성하고 연계시키려면
    현재 머리속에서는
    table당 어뎁터를 만들고 연계는 코딩부분에서 연계시키려 생각 중입니다.
    위의 방법은 너무 깔끔하지 못할것 같아서요

    안드로이드 자체에서 외래키등 지원을 하는지요

    그리고 한 어뎁터에서 여러개의 table을 생성가능한가요? database_name 이 같고 database_table의 이름이
    다르다면 가능하지 않을지 싶은데요

  7. 테이블당 어댑터를 만들 필요도 없고, 추천하지는 않지만 여러 개의 데이터베이스 파일을 하나의 어댑터로 관리할 수도 있습니다. 구현하기 나름이지요 :)

    안드로이드에서는 SQLite3를 사용하므로... DBMS에서 지원하는 기능은 그 쪽을 참고하시면 좋을 듯 합니다.

  8. Blog Icon
    AKaZe

    제가 여기 나와있는 예제를 바탕으로 table을 3개를 생성하였습니다.
    그런데 처음생성한 table은 정상작동을 합니다.

    그러나 db.execSQL(CREATE_TABLE2); db.execSQL(CREATE_TABLE3);
    과같이 3개를 1번째 table과 동일한 양식과 방법으로 생성해주었습니다.
    그런데 동일하게 하여 생성한 table임에도 불구하고 list에 뿌려주는 SimpleCursorAdapter 를 사용할 경우
    ndroid.database.sqlite.SQLiteException: no such column: _id <<<과 같은 런타임 에러를 뿜습니다.
    table이 생성되지 않은것일까요? log로 찍어보면 형이 다른것을 insert문장 수행시 삽입 에러가 뜨긴합니다.
    여러개의 table을 관리할수 있는 간단한 방법 여쭐수 있을까요?

  9. sqlite3 같은 툴로 테이블이 제대로 생성되었는지 우선 확인해보는 것이 우선인 듯 합니다. 어댑터를 사용하려면 해당 테이블에 _id 컬럼이 있어야 하는데, 에러 메시지로 봐선 그놈이 없다 하는 것 같군요.

  10. Blog Icon
    AKaZe

    커니님 답변감사드립니다. 글은 써놓고 이제야 확인해보네요 ^^;
    커니님 개인적인 질문하나 드릴게요

    table1 table2
    title data title data part date
    현금 0 현금 3000
    카드 0 카드 3000
    현금 -3000
    현금 -5000
    카드 -10000

    위와같이 table2개가 잇을경우
    table 1은 초기값이 위와같이 0으로 table이 create될때 insert문으로 초기값이 주어집니다
    update문으로 table1 의 data는

    (select SUM(data) from table2 where ='x'
    위에서 x는 cursor를 이용 table1의 모든 title을 반환받아
    title값 하나를 string에 저장해서 매개변수로 넘겨줍니다)

    위와같은 생각을 하고잇는데 생각보다 어렵네요

    Cursor c3 = aDbHelper.fetch();
    c3.moveToFirst();
    String msg;
    while(!c3.isAfterLast()){
    msg = c3.getString(0);
    aDbHelper.updateRow(msg);
    c3.moveToNext();
    }
    c3.close();

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

    public void updateRow(String x){
    mDb.execSQL("update account SET data = ( select SUM(data) from item where title = '"+x+"' ) where title = '"+x+"'";);
    }

    위와 같이 구성해봤는데 account 의 data가 널값으로 입력되는듯하네요 data를 not null로 세팅해서 오류 발생하고요

    혹 아래가 아래와 같은 쿼리문의 결과를 바로 적용시킬 방법도 존재하나요?

    public Cursor fetch2sum2(String x){
    return mDb.rawQuery("Select SUM(data) From item Where title = '"+x+"'", null);
    }

  11. Blog Icon
    teetree

    만약 테이블을 2개 생성시, data1, data2 일경우,
    db.execSQL("DROP TABLE IF EXISTS data1";);
    db.execSQL("DROP TABLE IF EXISTS data2";);
    이렇게 넣어야하는건가요? 너무 초보적인 질문입니다. ^^;;

  12. DROP은 테이블을 삭제할때 사용하는 명령어이고... CREATE를 사용하셔야 합니다 :)

  13. Blog Icon
    붕어

    커니님 기본적인 질문 하나 할께요 :D
    이미 있는 .db 파일을 포함시켜서 어플을 만들고 싶은데.
    sd카드에 넣으면 다른 사용자들은 이용을 하지 못할까 싶어서요..
    그건 어디 폴더에 넣고 불러와야 하나요 :)?

  14. assets라는 폴어데 db파일을 넣어서 배포할 수도 있고, 아니면 앱 다운로드 후 최초 실행시 DB를 인터넷에서 다운로드하도록 하는 방법도 있습니다.

    작은 용량의 DB라면 assets 폴더에 넣는 것이 편합니다 ㅎㅎ

  15. Blog Icon
    청은차

    커니님 강좌 잘보고있습니다.
    제가 제일 이해가 안되는 부분이 예제처럼 Context Menu 에서 edit 텍스트를 선택시
    edit 화면으로 넘어갈때 해당 db 인자를 어떻게 가지고 가는지 궁금하거든요...

    혹시 이예제 소스 공개 좀 해주시면 안될까요??
    아니면 약간의 힌트라도...ㅠㅠ onContextItemSelected 부분이 어떻게 처리되었는지 알고 싶습니다.
    수고하세요.

  16. onContextItemSelected() 의 인자로 id를 받는데, 이것을 통해 데이터베이스 내 해당 레코드의 id를 얻을 수 있습니다.

    이걸 이용하면 데이터베이스에 쿼리를 통해 해당 레코드의 정보를 받아오는 것이 가능합니다 :)

    그리고 이 코드 예제는 조만간 공개할 예정입니다! ㅎㅎ
    출간 예정인 책의 예제로 포함되어 있는데, 작업 완료되는대로 블로그에 올리겠습니다 :)

  17. Blog Icon
    idgocm

    xml에 만들어 놓은 edittext에다가 이름이랑 전화번호를 써서 db테이블 생성은 어떻게 해야 하나요? ㅠ 코드에다가 미리 안쓰고

  18. Blog Icon

    비밀댓글입니다

  19. 제 책의 예제 소스코드에 유사한 작업을 하는 애플리케이션이 있습니다. :)

    http://androidhuman.tistory.com/436 에 있는 소스코드 중 6장의 MyBookmark, MyBookmark_2 예제를 보시면 도움이 되실겁니다.

  20. Blog Icon
    MADscientist

    강좌 잘봤습니다.
    그런데 잘 이해가 안되는 부분이 있어서 질문 드립니다.
    private class DatabaseHelper extends SQLiteOpenHelper{
    부분에서
    데이터 베이스 열거나 생성하기 위해서 설계한 클래스라고 하셨는데 굳이 SQLiteOpenHelper 상속할 필요가 있는 것인지 궁금합니다...

  21. SQLIteOpenHelper는 말 그대로 데이터베이스 생성, 업그레이드, 열기 등을 수행해주는 기능을 포함하고 있습니다.

    데이터베이스를 사용하기 위해 DB를 여는 것 뿐만 아니라 데이터 추가, 삭제, 수정 등의 기능을 넣기 위해 이 클래스를 상속받는 것입니다.

  22. Blog Icon
    MADscientist

    public DbAdapter open() throws SQLException{
    mDbHelper = new DatabaseHelper(mCtx);
    mDb = mDbHelper.getWritableDatabase();
    return this;
    }
    부분에서

    getWritableDatabase() 뭐하는 메소드인가요???

  23. http://developer.android.com/reference/android/database/sqlite/SQLiteOpenHelper.html#getWritableDatabase()

    참고하세요.

  24. 커니님 SQLiteOpenHelper 사용시 Delete와 Update가 간헐적으로만 작동하는데 원인을 모르겠습니다. 도와주시면 감사하겠습니다.

    질문은 안드로이드 펍에 올려놓았습니다. (이걸로 이틀째 삽질중입니다.)

    꼭 좀 해결해주세요. ㅠㅠ;
    http://www.androidpub.com/android_dev_qna/1254194

    p.s : 토스트도 작동을 안하는데 미치겠네요.

  25. 디버깅을 한번 해보시는것을 추천드립니다. :(
    이런문제는 디버깅을 해보면서 원인을 찾아야 무엇이 문제인지 제대로 알 수 있습니다.

  26. Blog Icon
    하이바쓴애

    해결했습니다. 아는동생이 도와줬네요. 디버깅 해보니 SQL에서 rowID의 autoIncrement 때문이더군요.;;

  27. Blog Icon
    데이터베이스에서 막히다 ㅠ

    에러 없이 잘 돌아가는데 뭐가 문제인지 잘 모르겠습니다.분명히 DB파일도 생성되었구요.
    일단 c.getCount가 무조건 0이 나오네요 ;;


    <메인 액티비티 onCreate안>
    this.gdb=new GroupDBHelper(this);
    gdb.open();
    gdb.insert("새폴더", item.ISBN);
    int a=gdb.getCount("새폴더";); // 여기서 a값이 0이 나옵니다.. insert가 잘 안된거 같은데;;

    <DB 어댑터>
    public class GroupDBHelper {
    public static final String KEY_NAME="name";
    public static final String KEY_ISBN="ISBN";
    public static final String KEY_ID="_id";

    public static final String DB_NAME="RM_DB.db";
    public static final String DB_TABLE="GROUP";
    public static final int DB_VERSION=1;

    private static final String ClassName=GroupDBHelper.class.getSimpleName();
    private static final String[] COLS=new String[]{
    KEY_ID,KEY_NAME,KEY_ISBN };

    public static final String DB_CREATE="create table "+DB_TABLE+" (_id integer primary key autoincrement,"+
    "name text not null, ISBN text not null);";

    private SQLiteDatabase db;
    private DBOpenHelper dbOpenHelper;

    private final Context mCtx;

    private class DBOpenHelper extends SQLiteOpenHelper{

    public DBOpenHelper(Context context){
    super(context,GroupDBHelper.DB_NAME,null,GroupDBHelper.DB_VERSION);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
    // TODO Auto-generated method stub
    try{
    db.execSQL(DB_CREATE);
    }catch(SQLException e)
    {
    Log.e("DB",GroupDBHelper.ClassName, e);
    }
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVer, int newVer) {
    // TODO Auto-generated method stub
    db.execSQL("DROP TABLE IF EXISTS "+GroupDBHelper.DB_TABLE);
    this.onCreate(db);
    }
    }

    // end inner class

    public GroupDBHelper(Context context){
    this.mCtx=context;
    }
    public GroupDBHelper open()throws SQLException{
    dbOpenHelper=new DBOpenHelper(mCtx);
    db=dbOpenHelper.getWritableDatabase();
    return this;
    }
    public void cleanup(){
    if(this.db!=null){
    this.db.close();
    this.db=null;
    }
    }
    public void insert(String name,String ISBN){
    ContentValues values=new ContentValues();
    values.put(KEY_NAME, name);
    values.put(KEY_ISBN,ISBN);
    this.db.insert(GroupDBHelper.DB_TABLE, null, values);
    }
    public void update(long id,String name,String ISBN){
    ContentValues values=new ContentValues();
    values.put(KEY_NAME, name);
    values.put(KEY_ISBN,ISBN);
    this.db.update(GroupDBHelper.DB_TABLE, values, KEY_ID+"="+id, null);
    }

    public void delete(long id){
    this.db.delete(GroupDBHelper.DB_TABLE, KEY_ID+"="+id, null);
    }
    public int getCount(String name){
    Cursor c=null;
    int count=0;
    try{
    c=this.db.query(true, GroupDBHelper.DB_TABLE, GroupDBHelper.COLS, KEY_NAME+"="+name,
    null, null, null,null,null);
    if(c.getCount()>0){
    count=c.getCount();
    }
    }catch(SQLException e){
    Log.v("DB", GroupDBHelper.ClassName, e);
    }finally{
    if(c!=null && !c.isClosed()){
    c.close();
    }
    }
    return count;
    }
    public ArrayList<String> getAllName(){
    ArrayList<String> arr=new ArrayList<String> ();
    Cursor c=null;
    try{
    c=this.db.query(GroupDBHelper.DB_TABLE, GroupDBHelper.COLS,
    null, null, null,null,null);
    int count=c.getCount();
    c.moveToFirst();
    for(int i=0;i<count;i++)
    {
    String str=c.getString(1);
    if(arr.contains(str)==false){
    arr.add(str);
    }
    c.moveToNext();
    }
    }catch(SQLException e){
    Log.v("DB", GroupDBHelper.ClassName, e);
    }finally{
    if(c!=null && !c.isClosed()){
    c.close();
    }
    }
    return arr;
    }
    public ArrayList<String> getISBN(String name){
    ArrayList<String> arr=new ArrayList<String> ();
    Cursor c=null;
    try{
    c=this.db.query(true, GroupDBHelper.DB_TABLE, GroupDBHelper.COLS, KEY_NAME+"="+name,
    null, null, null,null,null);
    if(c.getCount()>0){
    c.moveToFirst();
    int num=c.getCount();
    for(int i=0;i<num;i++)
    {
    if(name==c.getString(1))
    {
    arr.add(c.getString(2));
    }
    c.moveToNext();
    }
    }
    }catch(SQLException e){
    Log.v("DB", GroupDBHelper.ClassName, e);
    }finally{
    if(c!=null && !c.isClosed()){
    c.close();
    }
    }
    return arr;
    }
    }

  28. 이럴땐 코드보다는 DB 파일을 직접 확인해보는게 더 빠릅니다. aqlite3 툴을 사용하여 데이터베이스를 직접 확인해보시고, 레코드가 들어가 있는지 먼저 확인해보시는걸 추천드립니다.

  29. 좋은글 출처를 표시하고 블로그에 담아갑니다. ^^

  30. Blog Icon
    초보

    private final Context mCtx;
    여기서 mCtx가 하는일이 뭔가요ㅠㅠㅠ?

  31. Context에 대한 내용은 휴우님의 글을 참고하시는게 좋겠네요~~
    http://blog.naver.com/PostView.nhn?blogId=huewu&logNo=110085457720

  32. Blog Icon
    안드초보

    안녕하세요. 커니님 매번 자료 볼때마다 말을 풀어서 쉽게 이해하게 해주셧는데, 이글은 뭔가 공부를 더해야될거같은 느낌이 퐉퐉듭니다 ㅠㅠ

    delete하는곳에서 return ... > 0 이렇게 표현해주셧는데, 여기서 " > 0"이게 어떤의미를 갖고있는건지 잘 모르겟네요.

    왜해주는건지 알수있을까요?

  33. delete() 메서드가 삭제한 행의 수를 반환하는데요, 실질적으로 삭제한 행이 있을 때 true를 반환하도록 하기 위해 위와 같이 작성했습니다~!

  34. Blog Icon
    졸작중

    안녕하세요! 강의 너무 잘 보고있습니다.
    이번 강의 보면서 궁금한게 있어 질문드립니다. 테이블 생성할때 자료형으로 왜 int를 안쓰고 integer을 썼는지 궁금합니다.
    프로그래밍 지식이 매우 낮은 학생인데, 저는 학교에서 테이블을 생성할때 항상 int로 했었거든요. 근데 왜 여기서 integer을 썼는지 궁금합니다. 인터넷에 int와 integer의 차이를 쳐봐도 무슨말인지 잘 모르겠어서 질문드립니다!
    항상 감사합니다!

  35. 두 개 모두 동일한 내용입니다~~
    SQLite에서는 정수형 데이터를 나타낼 때 INTEGER라는 표현을 쓰기에 그 부분에 맞춰 써준 것 말고 다른 부분은 없어요~

  36. Blog Icon
    ddd

    안녕하세요 커니님 매번 잘보고 많이 배우고 있습니다.

    이번에 SQLite 공부 해보면서 이 글을 활용해서 엑티브 1에서 ~2로 넘어가면서 리스트뷰에 뿌려지는 것을 만들고 있는데요 !
    정말 죄송한데 커니님 DBAdapter를 활용하려면 소스를 어떤식으로 짜야할까요 ..?

    다른글보면서 여기 디비어뎁터에 접목시키려고 보니깐 다 조금씩 달라서 많이 헤깔립니다.. 조금만 알려주세요 !!

  37. DBAdapter 형태를 그대로 사용하시려면 싱글톤(Singleton)형태로 사용하시는 것이 안전합니다. 그래야 세션이 중복되는 문제를 방지할 수 있습니다.