본문 바로가기

Android/DataBase

안드로이드/Android DB 생성 및 관리 ( Cursor , Query )

안드로이드/Android DB 생성 및 관리 ( Cursor , Query )


안드로이드 프로젝트를 진행하다 보면 DATA를 보관하고 사용하게 되는 경우가 자주 발생하게 됩니다. 보통 회원가입을 통해 회원정보를 저장할때 많이 사용하는데요. 그럴경우 DATA를 계속적으로 보관하고 사용해야 하기 때문에 DATABASE(=DB) 라는 저장공간에을 사용하게 되는 것 입니다.

안드로이드에서는 이러한 경우를 대비해 SQLiteDatabase라는 DATABASE 를 제공해 주는데요. 만약 DATA를 저장해서 사용하게 되는 경우가 생길 경우 
SQLiteDatabase 와 DatabaseHelper(DB생성 및 관리를 도와준다.) 를 사용해서 좀더 편리하게 DATABASE를 관리 할 수 있습니다.

DB를 사용하기 위해서는 우선 어떤 작업을 제일 먼저 해야할까요? 그렇습니다.ㅎ 일단 DB를 사용하기 위해서는 DB를 생성해야 합니다. DB는 TABLE 구조로 DATA를 관리하고있으므로 우선대야 할 작업이 TABLE 구조를 만드는 일입니다. 자 그럼 TABLE 구조를 만드는 코드를 보겠습니다.

// DataBase Table
public final class DataBases {
	
	public static final class CreateDB implements BaseColumns{
		public static final String NAME = "name";
		public static final String CONTACT = "contact";
		public static final String EMAIL = "email";
		public static final String _TABLENAME = "address";
		public static final String _CREATE = 
			"create table "+_TABLENAME+"(" 
					+_ID+" integer primary key autoincrement, " 	
					+NAME+" text not null , " 
					+CONTACT+" text not null , " 
					+EMAIL+" text not null );";
	}
}




여기서 주의해서 보실 부분중에 
autoincrement 란 속성이 있습니다. autoincrement 란 속성은 DATA를 DB에 넣을때 자동으로 "+1" 을 증가 시켜주는 주어 DATA를 식별 할 수 있는 고유 번호를 부여하는 속성 입니다. 위의 예제에서는 "_id" 라는 필드에 사용 되었습니다.





실제 위의 코드로 생성한 DB 테이블(address) 구조 입니다.
RecNo은 DB Tool 자체의 Number코드이니 "_id" 필드 부터 보시면 됩니다. 또한 "_id" 필드의 값이 +1씩 자동 증가한 모습을 볼 수 있습니다.




자 그럼, DB TABLE 구조에 대해 알아보았습니다. 다음은 
SQLiteDatabase 와  DatabaseHelper 를 통한 DB 관리 예제 설명 하겠습니다. 

public class DbOpenHelper {

	private static final String DATABASE_NAME = "addressbook.db";
	private static final int DATABASE_VERSION = 1;
	public static SQLiteDatabase mDB;
	private DatabaseHelper mDBHelper;
	private Context mCtx;

	private class DatabaseHelper extends SQLiteOpenHelper{

		// 생성자
		public DatabaseHelper(Context context, String name,
				CursorFactory factory, int version) {
			super(context, name, factory, version);
		}

		// 최초 DB를 만들때 한번만 호출된다.
		@Override
		public void onCreate(SQLiteDatabase db) {
			db.execSQL(DataBases.CreateDB._CREATE);

		}

		// 버전이 업데이트 되었을 경우 DB를 다시 만들어 준다.
		@Override
		public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
			db.execSQL("DROP TABLE IF EXISTS "+DataBases.CreateDB._TABLENAME);
			onCreate(db);
		}
	}

	public DbOpenHelper(Context context){
		this.mCtx = context;
	}

	public DbOpenHelper open() throws SQLException{
		mDBHelper = new DatabaseHelper(mCtx, DATABASE_NAME, null, DATABASE_VERSION);
		mDB = mDBHelper.getWritableDatabase();
		return this;
	}

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

}


위의 예제 소스에서 보시는 바와 같이,


 
onCreate(SQLiteDatabase db) : 최초 DB를 만들때 한번만 호출됩니다.

onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) : 버전이 업데이트 되었을 
경우 DB를 다시 만들어 줍니다. DATABASE_VERSION = 1, 2, 3 이런식으로 수정해 주시면 자동
으로 기존의 TABLE을 삭제하고 새로운 TABLE을 만들어 줍니다.

getWritableDatabase()  :  DB를 사용하기위해 생성하거나 열어 줍니다.
DB를 읽거나 쓸수 있는 권한을 부여 합니다.


 





다음은 DB를 사용하는 ACTIVITY 화면 입니다. 실제 사용자가 DB에 DATA를 저장, 삭제, 갱신, 조회 등의 작업을 수행하는 클래스 입니다. 

public class TestDataBaseActivity extends Activity {
	
	private static final String TAG = "TestDataBaseActivity";
	private DbOpenHelper mDbOpenHelper;
	private Cursor mCursor;
	private InfoClass mInfoClass;
	private ArrayList mInfoArray;
	private CustomAdapter mAdapter;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        setLayout();
        
        // DB Create and Open
        mDbOpenHelper = new DbOpenHelper(this);
        mDbOpenHelper.open();
        
        mDbOpenHelper.insertColumn("김태희","01000001111" , "angel@google.com");
        mDbOpenHelper.insertColumn("송혜교","01333331111" , "asdffff@emdo.com");
        mDbOpenHelper.insertColumn("낸시랭","01234001111" , "yaya@hhh.com");
        mDbOpenHelper.insertColumn("제시카","01600001111" , "tree777@atat.com");
        mDbOpenHelper.insertColumn("성유리","01700001111" , "tiger@tttt.com");
        mDbOpenHelper.insertColumn("김태우","01800001111" , "gril@zzz.com");
        
//        startManagingCursor(mCursor);
        
        
        mInfoArray = new ArrayList();
        
        doWhileCursorToArray();
        
        for(InfoClass i : mInfoArray){
        	DLog.d(TAG, "ID = " + i._id);
        	DLog.d(TAG, "name = " + i.name);
        	DLog.d(TAG, "contact = " + i.contact);
        	DLog.d(TAG, "email = " + i.email);
        }
        
        mAdapter = new CustomAdapter(this, mInfoArray);
        mListView.setAdapter(mAdapter);
        mListView.setOnItemLongClickListener(longClickListener);
        
    }
    
    @Override
    protected void onDestroy() {
    	mDbOpenHelper.close();
    	super.onDestroy();
    }
    
    
    /**
     * ListView의 Item을 롱클릭 할때 호출 ( 선택한 아이템의 DB 컬럼과 Data를 삭제 한다. )
     */
    private OnItemLongClickListener longClickListener = new OnItemLongClickListener() {
		@Override
		public boolean onItemLongClick(AdapterView arg0, View arg1,
				int position, long arg3) {
			
			DLog.e(TAG, "position = " + position);
			
			boolean result = mDbOpenHelper.deleteColumn(position + 1);
			DLog.e(TAG, "result = " + result);
			
			if(result){
				mInfoArray.remove(position);
				mAdapter.setArrayList(mInfoArray);
				mAdapter.notifyDataSetChanged();
			}else {
				Toast.makeText(getApplicationContext(), "INDEX를 확인해 주세요.", 
						Toast.LENGTH_LONG).show();
			}
			
			return false;
		}
	};
	
	
	/**
	 * DB에서 받아온 값을 ArrayList에 Add
	 */
	private void doWhileCursorToArray(){
		
		mCursor = null;
		mCursor = mDbOpenHelper.getAllColumns();
		DLog.e(TAG, "COUNT = " + mCursor.getCount());
		
		while (mCursor.moveToNext()) {
        	
			mInfoClass = new InfoClass(
					mCursor.getInt(mCursor.getColumnIndex("_id")),
					mCursor.getString(mCursor.getColumnIndex("name")),
					mCursor.getString(mCursor.getColumnIndex("contact")),
					mCursor.getString(mCursor.getColumnIndex("email"))
					);
			
			mInfoArray.add(mInfoClass);
		}
		
		mCursor.close();
	}
    
    
	/**
	 * OnClick Button
	 * @param v
	 */
    public void onClick(View v){
    	switch (v.getId()) {
		case R.id.btn_add:
			mDbOpenHelper.insertColumn
					(
					mEditTexts[Constants.NAME].getText().toString().trim(),
					mEditTexts[Constants.CONTACT].getText().toString().trim(),
					mEditTexts[Constants.EMAIL].getText().toString().trim()
					);
			
			mInfoArray.clear();
			
			doWhileCursorToArray();
			
			mAdapter.setArrayList(mInfoArray);			
			mAdapter.notifyDataSetChanged();
			
			mCursor.close();
			
			break;

		default:
			break;
		}
    }
    
    /*
     * Layout
     */
    private EditText[] mEditTexts;
    private ListView mListView;
    
    private void setLayout(){
    	mEditTexts = new EditText[]{
    			(EditText)findViewById(R.id.et_name),
    			(EditText)findViewById(R.id.et_contact),
    			(EditText)findViewById(R.id.et_email)
    	};
    	
    	mListView = (ListView) findViewById(R.id.lv_list);
    }
}


위의 코드는 사용자가 직접 입력한 값을 DB에 추가하고, ListView에 뿌려주고 있으며, 또한 사용자가 ListView의 Item을 롱클릭 하였을 경우, Item을 DB와 ListView에서 삭제 시켜주는 코드 입니다.






이런 형식으로 짜여진 코드 입니다. 그런데 테스트 도중 아주 중요한 문제점을 하나 발견 했습니다. 바로 ListView의 Item을 롱클릭해서 DB와 Adapter의 DATA를 지우는 도중에 자동증가된 "_id" 의 값이 ArrayList의 포지션값과 다르다는 것을... 읔..

쉽게말해 ArrayList는 해당 Position을 지우면, 1,2,3,4,5 중 4를 지우면 자동으로 1,2,3,4로 포지션이 재할당 됩니다. (5번 포지션이 4번으로 바뀌는 것이죠.) 그런데 "_id" 속성은 DATA를 DB에 삽입 할 때 마다 +1을 증가시키지만, 마찬가지로 1,2,3,4,5 번중에 4번을 지우면 1,2,3,5 의 index를 가지게 된다는 말입니다.


이렇듯 말이죠.

 

 
그러므로 위의 index값이 일치하지 않는 문제를 가지고 있는 코드 입니다. 헐. 그럼 DATA를 삭제 할 수 없는 것일까요? ㅎㅎ그렇지 않습니다. 주민등록 번호나 전화번호 처럼 고유한 값을 가지고 DATA 삭제 처리를 해주면 됩니다. 주민등록번호나 전화번호는 전세계 하나밖에 없는 고유한 번호이기 때문에 해당 번호를 지우게 되도 중복으로 지워지는 일을 방지 할 수 있습니다.


자 그럼, 코드에 대해 마자 분석을 해보겠습니다.




우선 DB에서 값을 받아오는 Cursor라는 녀석이 있습니다.
DB 테이블에 담긴 DATA를 받아오는 Cousor는 SQLiteDatabase

query(DataBases.CreateDB._TABLENAME, null, null, null, null, null, null);
매개인자 별로 테이블, 컬럼, 선택값, 선택값배열, 그룹, 조건절, 정렬등을 지정해 쿼리를 좀더 편리하게 쓸 수 있도록 하는 메서드 입니다.

rawQuery( "select * from address where name=" + "'" + name + "'" , null);
평소에 많이 보았던 전체 쿼리로 DATA를 다루는 메서드 입니다.

의 속성을 이용해서 DATA를 받아 올 수 있습니다.

커서를 이용해 해당 필드를 조회 하는 방법 입니다.

mCursor.getInt(mCursor.getColumnIndex("_id")),
mCursor.getString(mCursor.getColumnIndex("name")),
mCursor.getString(mCursor.getColumnIndex("contact")),
mCursor.getString(mCursor.getColumnIndex("email"))

해당 필드에 대한 값을 int, String 으로 얻어 올 수 있습니다.

또한 While 문며 ArrayList에 DaTa를 Add 시킬때,
mCursor.moveToNext() 란 속성을 이용했는데요. 이 속성은 커서가 다음 행에 커서를 이동 시키는 방법 입니다. 커서의 마지막 행까지 루프를 돌게 되는 방법 입니다.

startManagingCursor(mCursor) 란 속성은 Activity 딴에서 커서를 관리해 주는 메서드 입니다. 커서를 Close 하지 않고 이동하거나 하는 경우 메모리 문제와 오류가 발생 할 수 있기때문에 Activity 주기에 따라 커서를 Open , Close 해주는 아주 고마운 메서드인 셈 입니다.
(BUT, 그러나 Cursor객체는 DB에서 값을 받아온 후 바로 Close 해주는게 좋습니다. Cursor 객체가 할당하고 있는 메모리를 생각해서 말이죠.) 


mAdapter.notifyDataSetChanged() 이 메서드 역시 굉장히 중요한 메서드 입니다. ListView에 뿌려지는 Data들을 관리해주는 Adapter에게 "Data의 변동이 있으니 변경 사항을 적용해라." 라고 알려주는 메서드 입니다. 이 메서드를 호출 해야만 Data의 추가, 삭제가 ListView를 통해 보여지게 되니 정말로 중요한 메서드 입니다.









이렇게 해서 DB 관리 및 생성에 대해 알아 보았는데요. 간단하게 포스팅 할려고 했던게 너무 길어 지고 피곤한 하루 였기 때문에 설명에 호의적이지 못한 부분도 간혹(?) 있었던 거 같네요 ㅎㅎ 아무튼 끝까지 봐주셔서 감사하고 유용하게 사용 하셨으면 좋겠습니다. 

 오늘도, 건강한 하루 즐거운 하루 잘 마무리 하시구요!!
다음 포스팅때 뵙겠습니다.






파일첨부 :








출처 : 커니의 안드로이드 이야기 DB 예제 소스
URL :  http://androidhuman.tistory.com/