- Today
- Yesterday
- Total
메이쁘
[Android][Kotlin] Coroutine을 사용한 Room DB(DataBase) 핵심 정리 및 샘플 코드! 본문
[Android][Kotlin] Coroutine을 사용한 Room DB(DataBase) 핵심 정리 및 샘플 코드!
메이쁘 2020. 7. 1. 00:54안녕하세요.
최근 안드로이드에서 SQLite 사용에 도움되는 Room DB 라이브러리에 대해 알아봅시다.
*** Coroutine에 대해 짚고 넘어가고 싶다?
https://maivve.tistory.com/154
Room DB(Database) 란 ?
- AAC(Android Architecture Component) 중 하나.
- SQLite DB 를 보다 더 쉽게 사용할 수 있도록 하는 라이브러리.
- SQLite 기능을 사용하면서, Kotlin(또는 Java) 언어를 통해 직접 DB에 접근할 수 있다.
- 즉, 객체의 맵핑을 통해 DB에 직접 접근하는 ORM(Object Relational Mapping) 라이브러리
- DB에서 데이터를 송수신하는 작업이기 때문에 UI Thread인 Main Thread 에서 처리할 수 없고 Background 에서만 작업 처리가 가능하다. 보통 비동기 처리.
*** 왜 SQLite 대신에 Room DB를 사용할까? Room DB가 해결한 SQLite의 문제점?
1) SQLite 는 컴파일 시간이 확실하지 않다.
2) SQL 데이터에 변화가 생기면 직접 수동으로 변경해야 한다.
3) 위 과정에서 시간이 많이 소모되며 오류 발생 확률이 높다.
Room DB 구성요소
- Entity
-> DB의 테이블 과 매칭될 클래스.
-> 테이블에서 column 에 해당하는 정보들을 변수(parameter)로 담고 있다.
- DAO(Data Access Object)
-> DB 작업(CRUD)을 함수로 정의한 클래스. 즉, 실제로 DB에 접근하는 객체.
-> @Dao Annotation 을 가진 interface
-> return 값을 livaData로 받아오면서 최신 데이터 유지
-> Room은 DAO의 구현을 컴파일 시간에 생성한다.
- Room Database
-> DB 생성 및 버전 관리를 담당하는 Room DB 객체.
-> @Database Annotation 안에 사용할 entity 클래스를 포함시킴
-> Singleton(싱글톤) 패턴으로 만들어야 비용을 절약하고 데이터의 일치성을 보장할 수 있다.
음.. 이렇게만 적어놓으면 감이 덜 잡힐 것입니다.
이제부터 직접 코드를 작성하고 보면서 이해하는 과정을 진행하겠습니다.
*** 부제 : 로그 관리 테스트 앱
1. Gradle 추가
apply plugin: 'kotlin-kapt'
dependencies {
def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor
// 저희는 코루틴을 사용할 것이기 때문에 해당 라이브러리까지 코드 작성
implementation "androidx.room:room-ktx:$room_version"
// optional - RxJava support for Room
implementation "androidx.room:room-rxjava2:$room_version"
// optional - Guava support for Room, including Optional and ListenableFuture
implementation "androidx.room:room-guava:$room_version"
// Test helpers
testImplementation "androidx.room:room-testing:$room_version"
}
- 코루틴을 사용할 것이기 때문에 코루틴 까지만 작성하고, 하단 부분은 필요한 분들만 코드 추가하면 된다.
2. Entity 생성
/* User.kt */
@Entity(tableName = "USER")
data class User(
@PrimaryKey(autoGenerate = true) val id: Int, // autoGenerate == auto increment
@ColumnInfo(name = "userName") var userName: String, // 이름
@ColumnInfo(name = "nickName") var nickName: String, // 닉네임
@ColumnInfo(name = "addTime") var addTime: Long // 시간(Date to Long)
) {
constructor() : this(0, "", "", 0) // default 값
}
- USER 라는 이름을 가진 테이블 생성.
- PrimaryKey로 int type id 컬럼 추가.
*** 이 때, autoGenerate = true 를 통해 auto increment 로 진행됨
- addTime 컬럼 값은 Long type 이기 때문에, 안드로이드 상에서는 Date 로 변환해서 사용한다.
- constructor() : this(~) 의 괄호 내에 입력한 값은 만약 DB로 해당 컬럼 값에 null이 insert 될 경우 기본값 을 뜻한다.
3. DAO 생성
/* UserDAO.kt */
@Dao
interface UserDAO {
@Insert(onConflict = REPLACE)
suspend fun insertAll( users : User)
// onConflict : 중복된 Primary Key 값이 DB 내 존재할 경우 대체(REPLACE)
// 다른 방식을 사용하기 위해선 다른 값을 집어넣으면 됨
@Insert(onConflict = REPLACE)
suspend fun insert(user: User)
@Delete
suspend fun delete(user: User)
@Query("SELECT * FROM USER")
suspend fun getAllUsers() : List<User>
@Query("SELECT * FROM USER WHERE userName = :name")
suspend fun getUser(name: String) : User
}
- onConflict : DB 내 중복된 값이 존재할 경우(보통 Primary Key를 보고 판단) REPLACE(대체. 즉, update와 동일)
- Insert(삽입)은 @Insert, Delect(삭제)는 @Delete
- 쿼리문 내 변수로 값을 입력받아 넣고자 할 경우, :변수명 을 넣고, 호출 함수의 parameter 변수명과 일치시킨다.
*** "SELECT * FROM USER WHERE userName = :name"
- Coroutine을 사용하기 위해 fun 앞에 suspend 추가.
4. TypeConverter 생성
/* UserTypeConverter.kt */
class UserTypeConverter {
@TypeConverter
fun fromTimestamp(value: Long?) : Date? = value?.let { Date(it) }
@TypeConverter
fun dateToTimeStamp(date : Date?) : Long? = date?.time
}
- Date 객체를 사용하기 위함. (시간을 기록하기 위함)
- Date to Long, Long to Date 함수
- Room DB에서 기본 자료형이 아닌 객체를 사용하기 위한 Annotation : @TypeConverter
5. Room Database 클래스, DB 객체 생성
/* UserDatabase.kt */
@Database(entities = [User::class], version = 1)
@TypeConverters(UserTypeConverter::class) // TypeConverter를 Database에 포함
abstract class UserDatabase : RoomDatabase() {
abstract fun userDao(): UserDAO
}
- 클래스 생성
- TypeConverter 객체를 Database에 집어넣기 위한 Annotation 작성
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val db = Room.databaseBuilder(
applicationContext,
UserDatabase::class.java, "test-db" // DB 이름
).build()
}
}
- 메인액티비티에서 db 라는 Room Database 객체 생성
6. 활용
*** Coroutine 을 사용하여 Room DB 작업을 동기, 비동기 중 원하는대로 자유롭게 가능
*** 뿐만 아니라, 백그라운드 쓰레드에서 작업 진행 및 효율적인 순서를 설계, 처리 가능
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val db = Room.databaseBuilder(
applicationContext,
UserDatabase::class.java, "test-db" // DB 이름
).build()
var userName: String = "테스트입니다"
var nickName: String = "테스트야옹"
// runBlocking : 괄호 내의 코드가 전부 진행된 다음에야 괄호 밖을 벗어날 수 있다. 동기 처리 가능
runBlocking {
delay(1000L)
db.userDao().insert(User(0, userName, nickName, Date()))
Log.d("TEST", "[SUCCESS] value is saved! name : $userName And nickname : $nickName")
}
// IO 쓰레드에서 진행.
GlobalScope.launch(Dispatchers.IO) {
var user = db.userDao().getUser(userName)
Log.d("TEST", "[SUCCESS] value getted! name : ${user.userName} And nickname : ${user.nickName}")
}
}
}
결과 로그
- 정상적으로 출력 확인됨.
감사합니다!
궁금한 점, 보완할 점 있으면 바로 댓글달아주세요!
'Technology > Android - Android Studio' 카테고리의 다른 글
[Kotlin][Android] Android 내장 KeyStore API 를 사용하여 데이터를 암호화-복호화 하기! (0) | 2020.06.28 |
---|---|
[Android] Splash 화면을 위한 가장 효율적이고 쉬운 방법(+ Splash 화면의 존재 이유!) (1) | 2020.06.20 |
[Kotlin][JAVA] BottomSheetDialog 설명 및 사용 방법 (직접 커스텀해서 레이아웃 생성!) (0) | 2020.06.20 |
[Android Studio] xml Resourse string tag 불러와서 사용하는 방법 (0) | 2020.04.22 |
[Android Studio] Fragment Refresh(새로고침) 하는 방법 (0) | 2020.03.05 |