Chap15. Android Database 01

Dora·2020년 10월 24일
0

SQLite

: 클라이언트 어플리케이션에 주로 사용하는 경량 내장형 DBMS

; 모바일 기기의 APP의 데이터를 기기 내에 DB를 이용하여 저장해야 할 때 주로 사용된다. (다른 사람과 공유해야하는 데이터는 이거말고 외부 서버를 이용)

  • 관계형 데이터베이스다.
  • 라이브러리 형태로 적용되므로 import하여 쓴다.
  • 안드로이드, iOS, 웹브라우저 등에서 사용된다.

안드로이드 DB 사용

사용 과정

1. DB설계
: 요구사항, UI등을 고려하여 테이블을 설계

2. Helper 클래스 작성
: SQLiteOpenHelper를 상속받아 설계를 바탕으로 DB 테이블을 생성
// SQLiteOpenHelper는 package를 import하여 사용가능
// 테이블 생성 후 샘플 데이터를 추가할 수도 있다.

3. DB 사용
: DB에 접근이 필요할 때 Helper클래스 객체를 생성하여 DB를 사용할 수 있다.
// 객체를 생성할 때 용도를 지정할 수 있다. (Writable/Readable)

+) 필요에 따라 DTO, DAO등을 추가로 개발하여 사용가능하다.
DTO(Data Transfer Object) 데이터 전송 객체
DAO(Data Access Object) 데이터 접근 객체


DB 테이블 설계

사용자 요구사항

< 연락처 관리 어플 >
기능1. 연락처 분류 가능 : 친구 / 가족 / 기타
기능2. 이름으로 검색 가능


DB관점에서 생각해 볼 것

  • 연락처를 구성하는 정보
  • 분류 가능한 정보 넣기
  • 검색을 가능케하는 정보 넣기

DB파일 생성

CREATE TABLE contact_table (_id integer primary key autoincrement,
			name TEXT, phone TEXT, category TEXT)
  • 컬럼명은 _id 로 고정되어야 한다. (PK이므로)
    // 안드로이드는 기본적으로 _id를 PK로 갖는다.

Helper클래스 작성

SQLiteOpenHelper
: 안드로이드 DB를 편리하게 사용할 수 있도록 도와주는 클래스

클래스 상속 후 필수 사항

  1. 생성자 추가
    : 사용할 DB 파일 명 및 DB 버전을 지정한다.

  2. onCreate()/onUpgrade()재정의는 필수
    onCreate() : 사용할 테이블을 SQL을 사용하여 생성한다.
    onUpgrade() : 테이블 구조를 변경해야 할 필요가 있을 때 사용한다. 재정의하지 않아도 무방하긴 하다.


Helper가 생성한 DB 파일 접근

Android Studio에서 Device File Explorer 에서 확인 가능 (eclipse에서는 DDMS로 화면 전환 후 File Explorer 선택)

또는

DB파일을 컴퓨터에 저장한 후 SQLiteBrowser를 통한 확인도 가능

+) 실기기에서는 보안 문제로 폴더 외부 접근이 안되므로 에뮬레이터 상에서만 확인 가능하다.


실제 코드

package mobile.example.dbtest;

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

public class ContactDBHelper extends SQLiteOpenHelper {

	private final String TAG = "ContactDBHelper";

	private final static String DB_NAME = "contact_db"; 
	public final static String TABLE_NAME = "contact_table";
	public final static String COL_NAME = "name";
	public final static String COL_PHONE = "phone";
	public final static String COL_CAT = "category";

	public ContactDBHelper(Context context) {
		super(context, DB_NAME, null, 1);
	}

	@Override
	public void onCreate(SQLiteDatabase db) {
		String createSql = "create table " + TABLE_NAME + " ( _id integer primary key autoincrement,"
				+ COL_NAME + " TEXT, " + COL_PHONE + " TEXT, " + COL_CAT + " TEXT);";
		Log.d(TAG, createSql);
		db.execSQL(createSql);
	}

	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
		onCreate(db);
	}
}


private final static String DB_NAME = "contact_db"; 
public final static String TABLE_NAME = "contact_table";
public final static String COL_NAME = "name";
public final static String COL_PHONE = "phone";
public final static String COL_CAT = "category";

: 테이블명, 컬럼 명 등을 상수로 선언하여 수정이 용이하게 함.


public ContactDBHelper(Context context) {
		super(context, DB_NAME, null, 1);
}

: 생성자 선언 (DB파일 생성!)
; super()를 호출하여 생성할 DB파일을 지정

  • context : app이 실행되는 환경정보. Activity가 들어온다.
  • DB_NAME : 상수값에 들어있는 값으로 DB파일의 이름이 정해진다.
  • null : factory 정보
  • 1 : 버전 정보

@Override
	public void onCreate(SQLiteDatabase db) {
		String createSql = "create table " + TABLE_NAME + " ( _id integer primary key autoincrement,"
				+ COL_NAME + " TEXT, " + COL_PHONE + " TEXT, " + COL_CAT + " TEXT);";
		Log.d(TAG, createSql);
		db.execSQL(createSql);
	}

: onCreate 메소드 재정의 (DB Table 생성!)
; 상수값을 가져다 sql문을 만든다.
; Log를 사용한 디버깅으로 결과확인
; onCreate 메소드는 첫 호출 이후에는 호출되지 않음. 그러므로 수정 후에는 앱을 기기에서 지우고 다시 실행해야함.


@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
		onCreate(db);
	}

: onUpgrade 메소드 재정의
; DB버전이 변경된 경우 사용된다.
; DB를 제거하고 다시 onCreate하는 역할도 포함한다.


DB사용

1. helper 객체 생성
: DB클래스는 helper클래스에 의해 관리된다.
helper는 생성한 테이블을 보유하고 테이블 관련 query를 수행할 수 있다.

ContactDBHelper helper = new ContactDBHelper(this)

2. helper를 사용하여 SQLiteDatabase객체 획득
: help에서 해당 객체를 가져와 DB작업 수행

//읽기쓰기가능
SQLiteDatabase myDB = helper.getWritableDatabase()
or
//읽기가능
SQLiteDatabase myDB = helper.getReadableDatabase()

3. SQLiteDatabase 객체를 사용하여 Query 수행
: DB객체 획득 후 SQL을 직접 사용하거나 관련 메소드를 사용하여 query 수행

//CRUD 데이터 기본연산 메소드
myDB.insert()	// = create
myDB.update()
myDB.delete()
myDB.query()	// = select(read)
or
//SQL 연산
myDB.execSQL()	//insert, update, delete 기능
myDB.rawQuery()	// select 기능, Cursor 사용

4. helper 객체(및 관련 객체) Close 수행
: close하지 않으면 데이터 커밋이 안 일어날 수도 있음

helper.close()

SQLiteDatabase Query

Query에는 전용 메소드를 사용하는 방식과 SQL문을 사용하는 방식이 있다.

데이터 삽입

방법1. insert()
: ContentValues 객체 생성 후 입력할 데이터 값을 설정한 다음 insert()

ContentValues row = new ContentValues();
row.put("name", "컴퓨터");
row.put("phone", "1234");
row.put("category", "정보과학대학");
db.insert("contact_table, null, row);
  • put(컬럼명, 삽입할 값)
  • insert(table명, nullColumnHack, ContentValues)

방법2. execSQL()

db.execSQL("INSERT INTO contact_table VALUES (NULL, '컴퓨터', '1234', '정보과학대학');");


데이터 수정

방법1. update()

ContentValues row = new ContentValues();
row.put("phone", "0123");
String whereClause = "name=?"	//clause 절
String[] whereArgs = new String[]{"컴퓨터"};
db.update("contact_table", row, whereClause, whwerArgs);
  • put(수정할 컬럼명, 수정할 값)

  • whereClause가 수정할 값이 있는 레코드를 지정할 수 있는 조건column을 제시함. (null이 오면 전체 행 삭제를 의미)

  • whereArgs에는 whereClause에서 지목한 필드의 값을 입력

  • update(table명, ContentValues, 조건, 조건 값 배열)

+) whereClause에 name=? and phone=? 이 오면 whereArgs의 배열에는 값을 2개 넣으면 된다.


방법2. execSQL()

db.execSQL("UPDATE contact_table SET phone = '0123' WHERE name = '컴퓨터';");

데이터 삭제

방법1. delete()

String whereClause = "name=?";
String[] whereArgs = new String[]{"컴퓨터"};
db.delete("contact_table", whereClause, whereArgs);

방법2. execSQL()

db.execSQL("DELETE FROM contact_table WHERE name = '컴퓨터';");

데이터 검색

방법1. query() (메소드 사용)

String[] columns = {"_id", "name", "phone", "category"};
String selection = "name=?";
String[] selectArgs = new String[] {"컴퓨터"};

Cursor cursor = db.query("contact_table", columns, 
	selection, selectArgs, null, null, null, null);
  • String[] columns = null을 써도 위와 동일한 의미
  • query(table명, 읽어올 컬럼명, 조건, 조건 값 배열, gruopBy, having, orderBy, limit)

방법2. rawQuery() (SQL사용)

Cursor cursor = db.rawQuery("SELECT _id, name, phone, category
	FROM contact_table WHERE name = '컴퓨터';", null);

또는
Cursor cursor = db.rawQuery("SELECT * FROM contact_table 
	WHERE name = ?;", new String[] {"컴퓨터"}););

Cursor

: select문에 의해 반환한 레코드의 집합을 지정한다.

  • moveToNext()
    : 이동할 레코드가 있으면 true, 없으면 false를 반환

  • Cursor는 데이터 반환 집합에 대한 레퍼런스다.
    : get타입을 사용하여 값을 읽어온다. (읽어온 값은 cloumn순서)

  • Cursor는 사용 후 close() 해줘야 한다.

ex.

String result = "";
while (cursor.moveToNext()) {
	int id = cursor.getInt(0);
    String name = cursor.getString(1);
    String phone = cursor.getString(2);
    String category = cursor.getString(3);
    result += category + ":" + name + "-" + phone + "\n";
    //여기에 DTO나 list가 들어간다
}
cursor.close();

// cursor.get타입(column순서)보다 cursor.getColumnIndex(컬럼명)이 권장된다.

DTO 클래스

Data Transfer Object로 서로 다른 layer에 정보를 주고 받는 용도로 정의한 클래스.

  • VO(Vlaue Object)와 유사하다.
package mobile.example.dbtest;

import java.io.Serializable;

public class ContactDto implements Serializable {

	private long id;
	private String name;
	private String phone;
	private String category;
	
	
	public long getId() {
		return id;
	}
	public void setId(long id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getPhone() {
		return phone;
	}
	public void setPhone(String phone) {
		this.phone = phone;
	}
	public String getCategory() {
		return category;
	}
	public void setCategory(String category) {
		this.category = category;
	}
	
	@Override
	public String toString() {
		return id + ". " + category + " - " + name + " (" + phone + ")";
	}
	
}

public class ContactDto implements Serializable에서
implements Serializable를 하면 이 객체 자체를 intent에도 담을 수 있고, Serializable의 기능이 제공되는 곳에 이 DTO를 전달할 수 있다.


ListView와 DB의 연결

: ListView에 연결할 ArrayAdapter 또는 커스텀 Adapter에 ArrayList<아이템> 형태의 배열을 전달한다.

; 연결 시 새로운 데이터의 입력, 수정, 삭제에 따른 문제가 발생한다.
따라서 DB를 갱신할 경우 ArrayList의 갱신도 필수.

코드 적용

@Override
protected void onResume() {	//resume 재개하다
	super.onResume();

	SQLiteDatabase db = helper.getReadableDatabase();

	Cursor cursor = db.rawQuery("select * from " + 
    			ContactDBHelper.TABLE_NAME, null);

	contactList.clear();

	while(cursor.moveToNext()) {
		ContactDto dto = new ContactDto();
		dto.setId(cursor.getInt(cursor.getColumnIndex("_id")));
		dto.setName((cursor.getString(cursor.getColumnIndex(ContactDBHelper.COL_NAME))));
		dto.setPhone(cursor.getString(cursor.getColumnIndex(ContactDBHelper.COL_PHONE)));
		dto.setCategory(cursor.getString(cursor.getColumnIndex(ContactDBHelper.COL_CAT)));
		contactList.add(dto);
	}
	adapter.notifyDataSetChanged();

	cursor.close();
	helper.close();
}
  • Activity가 보여질 때 화면의 내용을 갱신하기 위하여 onResume()에 DB를 읽어오는 기능을 구현
contactList.clear();

clear 해주지 않으면 앱을 켤 때마다 동일한 내용이 ListView에 담긴다.


Cursor cursor = db.rawQuery("select * from " + 
			ContactDBHelper.TABLE_NAME, null);

위 코드는 sql을 직접 작성한 경우이다.

메소드를 사용하여 작성할 수도 있다. ↓

Cursor cursor = db.query(ContactDBHelper.TABLE_NAME, null, 
			null, null, null, null, null, null);

while(cursor.moveToNext()) {
	ContactDto dto = new ContactDto();
	dto.setId(cursor.getInt(cursor.getColumnIndex("_id")));
	dto.setName((cursor.getString(cursor.getColumnIndex(ContactDBHelper.COL_NAME))));
	dto.setPhone(cursor.getString(cursor.getColumnIndex(ContactDBHelper.COL_PHONE)));
	dto.setCategory(cursor.getString(cursor.getColumnIndex(ContactDBHelper.COL_CAT)));
	contactList.add(dto);
}

DTO객체 생성 후 읽어온 데이터의 각 컬럼에 해당하는 값을 저장 후 contactList(ArrayList)에 추가.


adapter.notifyDataSetChanged();

DB의 내용으로 갱신한 contactList(ArrayList)의 내용을 ListView에 반영하기 위해 호출.


ListView 항목을 사용한 DB접근

: Click 또는 LongClick시 pos 매개변수를 사용하여 contactList에서 해당 위치의 DTO 객체 추출

  • ListView와 ArrayList의 레코드 순서가 동일함을 이용한 것.
  • DTO객체의 getId()를 사용하여 _id값을 확인하고 이를 사용하여 DB작업

ListView에서 ArrayList를 이용할 땐 pos를 사용하고 DB를 이용할 땐 추출한 id를 이용한다.

Click 이벤트

AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener() {
	@Override
	public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
    		//id를 사용하여 DB 접근
    	}
};

LongClick 이벤트

AdapterView.OnItemLongClickListener itemLongClickListener = new AdapterView.OnItemLongClickListener() {
	@Override
    	public boolean onItemLongClickListener(AdapterView<?> parent, View view, int pos, long id) {
        	//id를 사용하여 DB 접근
        	return false;
        }
};
profile
Lv.1 개발자

0개의 댓글