-
Android Architecture Components #5 - RoomAndroid/AAC 2019. 8. 5. 18:04
대부분의 앱들은 대량의 데이터를 데이터베이스에 구조화 시켜서 영속적으로 저장한다.
이를 Room이라는 라이브러리를 사용하면 쉽게 작성이 가능하다.
구글에서는 안드로이드 앱의 데이터베이스 이용을 Room으로 작성할것을 강력하게 권고하고 있다.
ROOM은 ORM(Object Relational Mapping) 으로써 쉽게 말해 데이터베이스의 객체를 자바 or 코틀린 객체로 매핑해주는것 입니다.
ROOM은 SQLite의 추상레이어 위에 제공하고 있으며 SQLite의 모든 기능을 제공하면서 편한 데이터베이스의 접근을 허용합니다.
Room과 SQLite의 차이점
1. SQLite 경우 쿼리에 대한 에러를 컴파일에 확인하는것이 없지만 ROOM에서는 컴파일 도중 SQL에 대한 유효성을 검사 가능합니다
2. Schema가 변경이 될경우 SQL쿼리를 수동으로 업데이트 해야하지만 ROOM의 경우는 쉽게 해결이 가능합니다.
3. SQLite 경우 Java데이터 객체를 변경하기위해 많은 상용구 코드(Boiler Plate code)를 사용해야하지만 ROOM의 경우
ORM라이브러리가 상용구 코드(Boiler Plate code) 없이 매핑 가능합니다.
※ boilerPlateCode : 수정하지 않거나 최소한의 수정만을 거쳐 여러곳에 필수적으로 사용되는 코드
4. ROOM의 경우 LiveData와 RxJava를 위한 Observation 으로 생성하여 동작할 수 있지만 SQLite는 그렇지 않습니다.
ROOM의 3개 구성요소
ROOM에는 크게 3가지 구성요소(Database, Entity, Dao) 가 있습니다.
- Database : 데이터베이스 보유자입니다.
- Entity : Database 내의 테이블을 뜻합니다.
- Dao : 데이터베이스에 엑세스하는데 사용되는 메소드들을 갖고있습니다. select, insert, delete, join...등 데이터를 쓰거나 읽을때 사용합니다.
Gradle Dependency
def room_version = "2.1.0" implementation "androidx.room:room-runtime:$room_version" annotationProcessor "androidx.room:room-compiler:$room_version" // 선택 - 코틀린 확장 함수 또는 코투린을 룸에서 지원하는 라이브러리 implementation "androidx.room:room-ktx:$room_version" // 선택 - RxJava 를 룸에서 지원하는 라이브러리 implementation "androidx.room:room-rxjava2:$room_version" // 선택 - Guava 를 룸에서 지원하고 Optional 과 ListenableFuture 을 포함 implementation "androidx.room:room-guava:$room_version" // 테스트 도우미 testImplementation "androidx.room:room-testing:$room_version"
1. Entity Class 구현하기
Entity 클래스는 @Entity로 선언되는 클래스이다.
Entity 클래스의 데이터를 저장하기 위한 table이 자동으로 만들어진다.
table의 이름은 클래스 명이 되며 대소문자는 무시된다.
Entity 클래스 내의 field에 해당되는 Column이 생성된다.
@Entity( tableName = "user", indices = [Index(name = "name", unique = true)] ) class UserEntity( val userId: String, val userPw: String, @ColumnInfo("user_name") val userName: String )
@Ignore : 필드 중 DB에 저장하고 싶지 않은 필드에 사용합니다.
@primaryKey : 기본키를 설정합니다
( autoGenerate : DB를 하신 분들은 AutoIncrement 라고 생각하시면 됩니다.
0으로 설정시 자동으로 증가하며 그 외의 숫자를 지정 할 시 그 숫자 그대로 들어가게 됩니다. )
@ColumnInfo : 기본 컬럼명은 변수명으로 대체가 되지만 별도의 조작이 필요할 경우 사용 합니다.
@Entity( tableName = "todo", foreignKeys = [ ForeignKey( entity = UserEntity::class, parentColumns = ["userId"], childColumns = ["id"] ) ] ) class TodoEntity( @PrimaryKey(autoGenerate = true) val index: Int, val title: String, val context: String, val priority: Int, val userId: String )
@Index : 인덱스를 생성하며 결합 Index도 생성 가능합니다.
@foreignKey : ForeignKey도 생성이 가능합니다.
onDelete onUpdate를 이용해 child 데이터가 어떻게 처리를 해야할지를 명시한다.
[ Parent 기준 ] @onUpdate
CASCADE : parent row가 삭제, 수정되면 모든 child row를 삭제, 수정
NO_ACTION : parent key가 삭제, 수정되었다고 하더라도 child 쪽에서는 아무 일도 발생하지 않음
RESTRICT : 만약 child에 parent key로 매핑된 데이터가 있다면 parent 부분의 삭제 및 수정을 금지
SET_DEFAULT : parent 부분이 삭제, 수정시 child key column을 default 값으로 셋팅
SET_NULL : parent 부분이 삭제 되거나 수정되면 child key column을 null로 셋팅
class UserEntity( val userId: String, val userPw: String, val userName: String, val address: Address ) class Address( val street: String, val state: String, val city: String )
Nested Objects
Entity클래스가 Field로 Obejct를 같는 경우 @Embeded를 사용한다.
DB에는 Object의 컬럼도 하나의 컬럼으로 취급한다. 위의 UserEntity를 테이블로 만들면 컬럼은
userId / userPw / userName / street / state / city ) 총 6개의 컬럼이 생성된다.
만약 곂치는 객체가 있다면 prefix를 사용하여 이름을 설정한다.
2. Data Access Objects ( DAOs ) 구현하기
Dao는 abstract class나 interface가 될수 있다. RoomDatabase를 인자로 받는 생성자를 만드는 경우에만 abstract class가 될 수 있다. Room은 절대로 main thread에서 query 작업을 하지 않는다.
@Dao interface TodoDAO { @Insert fun insert(todo: TodoEntity) @Delete fun delete(todo: TodoEntity) @Update fun update(todo: TodoEntity) @Query("SELECT * FROM todo WHERE `index` = :id") fun select(id: Int) @Query("SELECT * FROM todo") fun selectAll(): List<TodoEntity> }
@Dao : 해당 인터페이스 또는 추상 클래스가 DAO 라고 명시
@Insert : 추가 ( @Entity 로 정의된 class, class의 collection 또는 array )
@Delete : 삭제 ( 삭제 기준은 PK를 기준 )
@Update : 수정 ( 수정 기준은 PK를 기준 )
@Query : 쿼리 ( SQL 문 작성 )
- SELECT 문에 Parameter가 들어가야 하는 경우 :parameter 형식으로 작성
- rx나 livedata를 사용할 경우 데이터 변동시 실시간으로 알려줌
- Join을 이용하여 여러 테이블을 access 할 수 있다.
- POJO 테이블을 return 할 수 있다.
3. Database 구현하기
@Database(entities = [TodoDAO::class], version = 1, exportSchema = false) abstract class TodoDatabase : RoomDatabase() { abstract fun getTodoDAO(): TodoDAO companion object { private var INSTANCE: TodoDatabase? = null fun getInstance(context: Context): TodoDatabase? { if (INSTANCE == null) { synchronized(TodoDatabase::class) { INSTANCE = Room.databaseBuilder( context.applicationContext, TodoDatabase::class.java, "TodoDB.db" ).build() } } return INSTANCE } } }
기본적으로 database를 생성하는 비용은 비싸기 때문에 싱글턴으로 생성하며 중복생성 되지 않도록 방지해야 한다.
Using type Converters
예를들어 DB에서는 TimeStamp로 되어 있고, Java Code에서는 Date class로 되어 있는 경우 우선 아래와 같이 Converter를 만들어야 한다.
object Converters { @TypeConverter fun fromTimestamp(value: Long?): Date? { return if (value == null) null else Date(value) } @TypeConverter fun dateToTimestamp(date: Date?): Long? { return date?.time } }
그리고 DB에 수정을 해준다.
@Database(entities = [TodoDAO::class], version = 1, exportSchema = false) @TypeConverters(Converters::class) abstract class TodoDatabase : RoomDatabase()
4. Databsae migration
databse migration이 필요한 경우 entity class에 수정항목을 반영해야 한다. 또한 데이터를 날리지 않기 위해서 migration을 할수있는 방법을 제공한다.
migration을 등록하면, runtime에 migration을 수행하며, 정해놓은 순서대로 migration이 가능하다.
migration을 등록할 때는 시작버전과 끝버전을 넣어야 한다.
companion object { private var INSTANCE: TodoDatabase? = null fun getInstance(context: Context): TodoDatabase? { if (INSTANCE == null) { synchronized(TodoDatabase::class) { INSTANCE = Room.databaseBuilder( context.applicationContext, TodoDatabase::class.java, "TodoDB.db" ).addMigrations(migration_1_2).build() } } return INSTANCE } val migration_1_2 = object : Migration(1, 2) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL("CREATE TABLE `rank` (`id` INTEGER, `userId` STRING)") } } }
'Android > AAC' 카테고리의 다른 글
Android Architecture Components #7 - WorkManager (0) 2019.08.06 Android Architecture Components #6 - Paging (0) 2019.08.06 Android Architecture Components #4 - Navigation (0) 2019.08.05 Android Architecture Components #3 - LiveData (0) 2019.07.31 Android Architecture Components #2 - ViewModel (0) 2019.07.31