이것이 안드로이드다 with 코틀린(고돈호 지음) 으로 공부한 내용을 정리한 글입니다.
안드로이드에서 사용하는 데이터베이스인 SQLite는 관계형 데이터베이스입니다. 관계형 데이터베이스는 데이터의 저장 형태와 관계를 정의하는 데이터베이스입니다.
테이블은 한 종류의 데이터들이 저장되는 단위입니다. 데이터의 속성은 column으로 구분하며, 각 column에 값이 채워진 한 줄의 데이터 단위를 row라고 합니다.
관계형 데이터베이스는 SQL이라는 데이터를 정의, 조작, 제어하는 언어를 사용합니다. 그리고 SQL의 명령어를 쿼리라고 합니다.
SELECT 속성
FROM 테이블
WHERE 조건
쿼리의 종류
1. DDL(Data Definition Language): 데이터의 구조를 정의하는 명령어
2. DML(Data Manipulation Language): 데이터를 조작하는 명령어
3. DCL(Data Control Language): 데이터베이스 권한과 관련된 명령어
SQLite는 안드로이드의 기본 데이터베이스이자 경량화된 데이터베이스입니다.
DDL문을 사용하여 테이블을 생성합니다
CREATE TABLE 테이블명 (
[속성1] [타입] [옵션], [속성2] [타입], ...
)
속성의 타입
- INTEGER: 정수형 속성
- TEXT: 문자형 속성
- REAL: 실수형 속성
- BLOB, NUMERIC 등
CREATE TABLE memo (
no INTEGER PRIMARY KEY,
content TEXT,
datetime INTEGER
)
PRIMARY KEY
로 설정된 속성이 INTEGER
타입일 때, 데이터가 추가되면 자동으로 이전 데이터에서 1 증가한 수로 설정됩니다.
SQLite를 사용하기 위해서는 Context
의 createDatabase()
메서드를 사용하거나, SQLiteOpenHelper
라는 클래스를 상속받아서 사용합니다.
SQLiteOpenHelper
클래스는 데이터베이스를 파일로 생성하고 코틀린 코드에서 사용할 수 있도록 데이터베이스와 연결하는 역할을 합니다.
class SqliteHelper(context: Context, name: Sring, version:Int) :
SQLiteOpenHelper(context, name, null, version) {
override fun onCreate(db: SQLiteDatabase?) {
val create = "create table memo (" +
"no interger primary key, " +
"content text, " +
"datetime integer" +
+)"
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
}
}
추가로 SQLite를 통해 INSERT, SELECT, UPDATE, DELETE
할 때 필요한 데이터 클래스도 정의해야 합니다.
data class Memo(var no: Long?, var content: String, var datetime: Long)
숫자 범위의 차이로 인해 데이터베이스에서 INTEGER
로 정의한 속성은 데이터 클래스에서 Long
으로 정의해야 합니다.
SQLiteOpenHelper를 이용하여 데이터를 삽일할 때는 키와 값의 쌍 형태로 사용되는 ContentValues
클래스를 이용합니다.
fun insertMemo(memo: Memo) {
val values = ContentValues()
values.put("content", memo.content)
values.put("datetime", memo.datetime)
val wd = writeableDatabase
wd.insert("memo", null, values)
wd.close()
}
fun selectMemo(): MutableList<Memo> {
val list = mutableListOf<Memo>()
val select = "select * from memo"
val rd = readableDatabase
val cursor = rd.rawQuery(select, null)
while(cursor.moveToNext()) {
val no = cursor.getLong(cursor.getClumnIndex("no"))
val content = cursor.getString(cursor.getColumnIndex("content")
val datetime = cursor.getLong(cursor.getColumnIndex("datetime")
list.add(Memo(no, content, datetime))
}
cursor.close()
rd.close()
return list
}
PRIMARY KEY
를 통해 데이터베이스 상의 위치를 지정하고 데이터를 수정합니다.
fun updateMemo(memo: Memo) {
val values = ContentValues()
values.put("content", memo.content)
values.put("datetime", memo.datetime)
val wd = writableDatabase
wd.update("memo", values, "no = ${memo.no}", null)
wd.close()
}
fun deleteMemo(memo: Memo) {
val delete = "delete from memo where no = ${memo.no}"
val db = writableDatabase
db.execSQL(delete)
db.close()
}
ORM(Object-Relational Mapping)은 객체와 관계형 데이터베이스의 데이터를 매핑하고 변환하는 기술이며 안드로이드는 SQLite를 코드 관점에서 접근할 수 있도록 ORM 라이브러리인 Room을 제공합니다.
Room은 어노테이션을 사용하기 때문에 Annotaiton Processing
를 위한 kapt
플러그인을 추가해야 합니다.
apply plugin: 'kotlin-kapt'
// ...
def room_version = "2.2.0 "
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
어노테이션
@명령어
형태를 가지며, 주석 형태의 문자열을 실제 코드로 생성해주는 것을 말함. 컴파일 시에 코드로 생성됨
@Entity
어노테이션이 적용된 클래스는 테이블로 변환됩니다.
@Entity(tableName = "orm_memo")
class RoomMemo {
@PrimaryKey(autoGenerate=true)
@ColumnInfo
var no: Long? = null
@Ignore
var temp: String = "임시로 사용되는 데이터"
}
@ColumnInfo
어노테이션이 적용된 변수는 테이블의 속성으로 변환되며 추가로 @PrimaryKey
어노테이션을 적용하면 테이블의 primary key로 사용됩니다. @Ignore
어노테이션은 테이블과 관계 없는 변수라는 것을 명시하는 것입니다.
Room은 데이터베이스에 읽고 쓰는 메서드를 인터페이스 형태로 설계하고 사용합니다.
DAO(Data Access Object)
데이터베이스에 접근해서 DML 쿼리를 실행하는 메서드의 모음
@Dao
어노테이션을 통해 DAO
인터페이스임을 명시합니다.
@Dao
interface RoomMemoDao {
@Query("select * from orm_memo")
fun getAll(): List<RoomMemo>
@Insert(onConflict = REPLACE)
fun insert(memo: RoomMemo)
@Delete
fun delete(memo: RoomMemo)
}
Room은 다른 ORM 툴과는 다르게 SELECT
쿼리를 직접 작성하도록 설계되어 있습니다. 따라서 @Query
어노테이션을 사용한 뒤 쿼리를 작성해야 합니다.
@Insert
어노테이션에 onConflict = REPLACE
옵션이 설정되고 삽입한 데이터와 동일한 키를 가진 데이터가 이미 있는 경우 UPDATE
쿼리로 실행됩니다.
SQLiteOpenHelper
처럼 Room도 RoomDatabase
클래스가 존재합니다. 다만 SQLiteOpenHelper
와 달리 추상 클래스로 생성해야 합니다.
@Database(entities = arrayOf(RoomMemo::class), version = 1, exportSchema = false)
abstract class RoomHelper : RoomDatabase() {
// Dao 인터페이스의 구현체를 사용할 수 있는 메서드명 정의
abstact fun roomMemoDao(): RoomMemoDao
}
@Database
어노테이션을 작성하여 여러 속성을 적용합니다. entities
는 Room이 사용할 테이블 클래스 목록, version
은 데이터베이스의 버전, exportSchema
는 스키마 정보를 파일로 출력할지 여부를 설정합니다.