데이터베이스는 다음과 같은 순서로 생성되고 사용된다는 것
데이터베이스 4단계
SQLite는 안드로이드에서 사용할 수 있는 경량급(Light-weight) 관계형 데이터베이스로, 표준 SQL문을 이용해 데이터를 조회할 수 있다.
임베디드 데이터베이스로 만들어진 SQLite는 파일 기반으로 동작 하면서도 데이터베이스의 기능을 그대로 사용할 수 있다. 안드로이드 운영체제에 자체적으로 탑재되어있기 때문에 MySQL, Oracle 등 서버단에서 주로 사용되는 관계형 데이터베이스보다 가볍다.
데이터베이스는 여러 개의 테이블을 가지고 있는 저장소같은 역할을 하며, 하나의 파일로 만들어진다. 처음에 한 번 create 해두면 그 뒤로는 만들어진 데이터베이스를 open해서 사용한다. 이 두개의 메서드는 Context
클래스를 상속받으므로 액티비티 클래스 안에서 데이터베이스를 만들거나 열 수 있다.
openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory)
: 데이터베이스를 생성하거나 만들어진 데이터베이스를 연다.
SQLiteDatabase db = openOrCreateDatabase(
"database", // 데이터베이스의 이름
MODE_PRIVATE, // 다른 앱에서의 접근 가능 범위
null // 쿼리 결과로 리턴되는 커서를 만들 객체
);
deleteDatabase(String name)
: 전달받은 이름의 데이터베이스를 삭제한다.
execSQL(String sql)
: 테이블을 새로 만드는 등 표준 SQL을 이용하기 위해 사용한다.
테이블을 생성할 땐 execSQL에 테이블을 생성하는 쿼리문을 문자열로 전달
예제
class MainActivity extends AppCompatActivity {
SQLiteDatabase db;
static final String DB_NAME = "database";
static final String TABLE_NAME = "people";
...
void createDatabase() {
db = openOrCreateDatabase(
DB_NAME, // 데이터베이스의 이름
MODE_PRIVATE, // 다른 앱에서의 접근 가능 범위
null // 쿼리 결과로 리턴되는 커서를 만들 객체
);
}
void createTable() {
db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + "(" +
"_id INTEGER PRIMARY KEY AUTOINCREMENT," +
"name TEXT," +
"age INTEGER);");
}
void insertRecode(String name, int age) {
db.execSQL("INSERT INTO " + TABLE_NAME +
" (name, age) VALUES (" +
"'" + name + "', "
+ age + ");");
}
...
}
SQLite를 사용할 때 데이터베이스 이름, 테이블 이름, 열 이름 등 동일한 문자열을 반복해서 써야하는 경우가 잦다.이 모든 명칭을 하드코딩 하게된다면 코드의 양이 늘어날수록 수정 및 오타 발견에 있어 불편함이 커질 것이다.Contact Class
는 데이터베이스에 필요한 각종 상수를 한번에 모아서 관리하는 역할을 한다.변경사항이 생기더라도 Contact Class에서 한 번만 변경을 하면 되므로 수정 작업이 용이해진다.
위에서 생성한 people 테이블에 대한 Contact Class의 예시
public final class PeopleContract {
private PeopleContract() {
}
// 하나의 테이블에 필요한 내용을 하나의 클래스에 정의한다.
public static class PeopleEntry implements BaseColumns {
public static final String TABLE_NAME = "people";
public static final String COLUMN_NAME = "name";
public static final String COLUMN_AGE = "age";
public static final String SQL_CREATE_TABLE =
"CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
_ID + " INTEGER PRIMARY KEY," +
COLUMN_NAME + " TEXT," +
COLUMN_AGE + " INTEGER)";
public static final String SQL_DELETE_TABLE =
"DROP TABLE IF EXISTS " + TABLE_NAME;
}
}
Contract Class의 인스턴스화를 방지하기 위해 생성자의 접근 지정자를 private로 지정.
여기서 하나의 inner 클래스는 하나의 테이블에 매치된다.BaseColumns
를 상속받는 것은 선택인데, 만약 상속한다면 내부적으로 _ID
열과 레코드 개수를 저장하는 _COUNT
가 추가된다.
스키마나 테이블의 구조가 변경될 경우 openOrCreateDatabase()를 사용하는 방식은 위험하고 비효율적일 수 있다. 따라서 데이터베이스를 생성할 땐 SQLiteDatabase보다 SQLiteOpenHelper를 사용하는 것이 권장된다. SQLiteOpenHelper를 상속받은 helper 클래스는 데이터베이스를 만들거나 여는 역할을 한다.
public SQLiteOpenHelper (Context context, String name,
SQLiteDatabase.CursorFactory factory, int version)
openOrCreateDatabase와 달리 version이라는 매개변수가 존재한다.데이터베이스가 변경되었을 때 버전 정보를 다르게 지정해 데이터베이스의 구조를 변경할 수 있다.
다음은 helper 클래스에서 데이터베이스 파일을 만들기 위한 메서드들이다.
getReadableDatabase()
: 읽기 전용 데이터베이스를 생성하거나 연다.getWritableDatabase()
: 쓰기 전용 데이터베이스를 생성하거나 연다.콜백 메서드를 재정의 해두면 데이터베이스의 생성과 수정 등 변경이 발생했을 때 상태에 따른 처리를 할 수 있다.
onCreate()
: 데이터베이스를 생성했을 때onOpen()
: 생성된 데이터베이스가 열렸을 때onUpgrade()
: 데이터베이스를 수정했을 때public class PeopleDBHelper extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "database";
public static final int DATABASE_VERSION = 1;
public PeopleDBHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL(PeopleEntry.SQL_CREATE_TABLE); // 테이블 생성
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
// 단순히 데이터를 삭제하고 다시 시작하는 정책이 적용될 경우
sqLiteDatabase.execSQL(PeopleEntry.SQL_DELETE_TABLE);
onCreate(sqLiteDatabase);
}
...
}
insert()
메서드에 ContentValues 객체를 넘기면 레코드의 삽입 작업이 처리된다.
people 테이블에 새로운 데이터를 추가하는 코드
public void insertRecord(String name, int age) {
// 읽기 전용 DB를 가져온다
SQLiteDatabase db = getReadableDatabase();
// column name을 key로 테이블에 삽입할 값의 집합을 생성한다.
ContentValues values = new ContentValues();
values.put(PeopleEntry.COLUMN_NAME, name);
values.put(PeopleEntry.COLUMN_AGE, age);
db.insert(PeopleEntry.TABLE_NAME, null, values);
}
query()
메서드로 조회한 데이터를 읽기 위해 Cursor 객체를 이용한다.
Coursor를 사용하면 조회된 레코드를 하나씩 참조하면서 데이터를 꺼내볼 수 있디.
moveToNext()
: Cursor를 다음 row로 이동시킨다.moveToPrevious()
: Cursor를 이전 row로 이동시킨다.moveToPosition(int i)
: Cursor를 특정 index로 이동시킨다.public Cursor readRecordOrderByAge() {
SQLiteDatabase db = getReadableDatabase();
String[] projection = {
BaseColumns._ID,
PeopleEntry.COLUMN_NAME,
PeopleEntry.COLUMN_AGE
};
String sortOrder = PeopleEntry.COLUMN_AGE + " DESC";
Cursor cursor = db.query(
PeopleEntry.TABLE_NAME,
projection, // 값을 가져올 column name의 배열
null, // where 문에 필요한 column
null, // where 문에 필요한 value
null, // group by를 적용할 column
null, // having 절
sortOrder // 정렬 방식
);
return cursor;
}