위시리스트를 저장하고 싶어!
안드로이드 어플에 데이터를 저장하는 방법은 여러가지가 있지만, 저는 서버가 없는 관계로 로컬 db인 SQLite를 사용하기로 했습니다.
Room
은 SQLite 성능을 최대화하면서도 원활한 데이터베이스 접근을 위해 추상 레이어를 제공합니다...만 물론 직접 접근하는 방법도 존재합니다. 아래 링크는 SQLite에 직접 접근하는 방법에 대해 설명하는 안드로이드 공식 문서입니다.
아래는 Room 관련 공식 문서입니다.
Room의 세 가지 구성 요소
Room
에는 세 개의 주요 구성 요소가 있습니다.
-
Database
: 데이터베이스 홀더를 포함하며, 데이터베이스 메인 진입점으로 사용됩니다.@Database
라 명시된 클래스는 다음 사항을 만족해야 합니다.-
RoomDatabase
를 확장한 추상 클래스일 것 -
데이터베이스와 연관된 엔티티 리스트를 포함할 것
-
아무 파라미터도 받지 않고,
@Dao
를 반환하는 추상화 메소드를 포함할 것실행 시점에
Room.databaseBuilder()
나Room.inMemoryDatabaseBuilder()
를 통해Database
인스턴스를 이용할 수 있습니다.
-
-
Entity
: 데이터베이스 속 테이블을 나타냅니다. -
DAO
: 데이터베이스에 접근하기 위한 메소드를 포함합니다.
앱은 Room 데이터베이스를 사용하여 Data Access Object, DAO를 획득합니다. 그다음 앱은 DAO를 사용해서 엔티티를 얻거나, 변경된 엔티티를 데이터베이스에 저장합니다. 마지막으로, 앱은 엔티티를 사용해서 컬럼 값을 얻거나 설정합니다.
예시 코드를 보면 Entity는 data class, DAO는 interface, Database는 abstract class로 표현되는 걸 볼 수 있습니다. 그리고 각각에 @Entity, @Dao, @Database 어노테이션이 붙어있네요.
// User.kt
@Entity
data class User(
@PrimaryKey val uid: Int,
@ColumnInfo(name = "first_name") val firstName: String?,
@ColumnInfo(name = "last_name") val lastName: String?
)
// UserDao.kt
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getAll(): List<User>
@Query("SELECT * FROM user WHERE uid IN (:userIds)")
fun loadAllByIds(userIds: IntArray): List<User>
@Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
"last_name LIKE :last LIMIT 1")
fun findByName(first: String, last: String): User
@Insert
fun insertAll(vararg users: User)
@Delete
fun delete(user: User)
}
// AppDatabase.kt
@Database(entities = arrayOf(User::class), version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
사용은 이렇게 하면 된다고 하네요.
val db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "database-name"
).build()
db.userDao().getAll()
Database의 중복 생성을 막기 위해서는 이렇게 싱글톤 구성하면 좋다고 합니다.
// AppDatabase.kt
@Database(entities = arrayOf(User::class), version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
private var INSTANCE: AppDatabase? = null
fun getInstance(context: Context): AppDatabase? {
if (INSTANCE == null) {
synchronized(AppDatabase::class) {
INSTANCE = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"db.db"
).build()
}
}
return INSTANCE
}
}
}
아래처럼 바로 부를 수 있습니다.
AppDatabase.userDao.getAll()
AppDatabase_Impl does not exist??
알아서 import 해줄 줄 알고 코드만 적고 빌드를 했더니 다음과 같은 에러가 뜹니다. dependencies는 직접 추가해주어야 하나봅니다.
아래 코드를 모듈의 build.gradle dependencies에 추가해주면 됩니다.
dependencies {
...
implementation "androidx.room:room-runtime:2.2.3"
kapt "androidx.room:room-compiler:2.2.3"
}
Cannot access database on the main thread ...
안드로이드에는 UI(Main) Thread와 Background Thread가 있습니다. 그 중 UI Thread는 긴 시간이 걸리는 작업을 중단하고 에러를 발생시킵니다. 때문에 db 처리 작업을 하려하면 아래와 같은 에러가 발생합니다.
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
at androidx.room.RoomDatabase.assertNotMainThread(RoomDatabase.java:267)
at androidx.room.RoomDatabase.query(RoomDatabase.java:323)
at androidx.room.util.DBUtil.query(DBUtil.java:83)
...
코틀린의 경우 Coroutine을 사용하여 이를 background Thread에서 처리합니다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val appDatabase = AppDatabase.getInstance(this)
CoroutineScope(Dispatchers.IO).launch {
appDatabase?.wishDao()?.getAll()
}
}
}
AUTOINCREMENT는 어떻게 하나요?
기존 SQLite 문법을 어떻게 사용해야 할 지 고민하던차, PrimaryKey 문서에서 확인할 수 있었습니다.
값을 추가할 때 uid 값을 자동 증가시키고 싶다면 아래처럼 작성하시면 됩니다.
// User.kt
@Entity
data class User(
@PrimaryKey(autoGenerate = true) val uid: Int,
@ColumnInfo(name = "first_name") val firstName: String?,
@ColumnInfo(name = "last_name") val lastName: String?
)
appDatabase?.userDao()?.insert(User(0, "엥", "휴"))
Int와 Long 타입은 다음과 같은 식으로 autoGenerate 되는 값을 0으로 두면 insert 과정에서 값이 자동으로 증가합니다. Integer의 경우, null로 처리했을 때 값이 자동 증가합니다. 이때 값은 1부터 시작합니다. 이걸 몰라서 아무 Int나 넣었다가 자꾸 에러가 났습니다...
@ColumnInfo는 무엇인가요?
@ColumnInfo는 해당 변수가 데이터베이스 내의 Column임을 나타내줍니다.
없어도 크게 상관은 없는 듯 보이네요??
Looks like you've changed schema but forgot to update the version number.
@Database(entities = arrayOf(User::class), version = 1)
저기 나와있는 저 버전을 높이라는데, 그렇게 하니 추가 에러가 발생해서 그냥 db 파일 이름을 바꾸거나 앱을 지웠다 까는 걸로 해결했습니다. 아래 글에서 Database migration
부분을 참고하셔도 좋을 것 같습니다.
'Android' 카테고리의 다른 글
LottoWish(로또위시) 개인정보 처리 방침 (0) | 2020.04.20 |
---|---|
kotlin 이슈 8 BottomNavigationView + ViewPager2 이용하여 슬라이드 메뉴 만들기 (0) | 2020.02.23 |
Kotlin 이슈 6 fragment lifecycle (0) | 2020.02.01 |
Kotlin 이슈 5 include 대신 정적 fragment, Unresolved reference. (0) | 2020.02.01 |
Kotlin 이슈 4 기능 별로 레이아웃 나누기, include (0) | 2020.02.01 |