-
[Android] 데이터 저장 - Room안드로이드 2024. 4. 30. 17:08
✏️ TIL(Today I Learned)
Room 대해 알아보았다.
Room은 안드로이드 플랫폼을 위한 객체 관계 매핑 (ORM) 라이브러리로, SQLite 데이터베이스를 보다 쉽게 사용할 수 있도록 도와준다.
EdtiText에 입력된 ID와 이름을 Datebase에 ADD, QUERY, DELETE 할 수 있는 버튼을 만들었다.
이름을 통해 QUERY 하고, ID를 통해 DELETE 되도록 했다.
App Inspection에서 Database를 확인하면 잘 저장된 것을 볼 수 있다.
Room을 사용하려면, gradle 파일 설정을 해야 한다.
build.gradle(:app)에 아래 내용을 추가하면 된다.
plugins { ... id ("kotlin-kapt") } dependencies { ... // Room dependencies val room_version = "2.6.1" implementation ("androidx.room:room-runtime:$room_version") annotationProcessor ("androidx.room:room-compiler:$room_version") kapt ("androidx.room:room-compiler:$room_version") // optional - Kotlin Extensions and Coroutines support for Room implementation ("androidx.room:room-ktx:$room_version") // optional - Test helpers testImplementation ("androidx.room:room-testing:$room_version") }
그다음, Entity를 만들어서 테이블 스키마 정의한다.
@Entity(tableName = "student_table") // 테이블 명 지정: student_table data class Student ( @PrimaryKey @ColumnInfo(name = "student_id") val id: Int, val name: String )
그리고 DAO를 생성한다. Dao는 interface나 abstract class로 정의되어야 한다.
Annotation에 SQL 쿼리를 정의하고 그 쿼리를 위한 메소드를 선언하면 된다. 가능한 annotation으로 @Insert, @Update, @Delete, @Query가 있다.
@Query로 리턴되는 데이터의 타입을 LiveData<>로 하여, 나중에 이 데이터가 업데이트될 때 Observer를 통해 할 수 있게 했다.
@Query에 SQL을 정의할 때, 메소드의 인자를 사용했다.
@Dao interface MyDAO { @Insert(onConflict = OnConflictStrategy.REPLACE) // INSERT, key 충돌이 나면 새 데이터로 교체 suspend fun insertStudent(student: Student) @Query("SELECT * FROM student_table") fun getAllStudents(): LiveData<List<Student>> // LiveData<> 사용 @Query("SELECT * FROM student_table WHERE name = :sname") // 메소드 인자를 SQL문에서 :을 붙여 사용 suspend fun getStudentByName(sname: String): List<Student> @Delete suspend fun deleteStudent(student: Student) }
마지막으로, Database 생성한다.
RoomDatabase를 상속하여 자신의 Room 클래스를 만들어야 한다.
포함되는 Entity들과 데이터베이스 버전(version)을 @Database annotation에 지정한다.
DAO를 가져올 수 있는 getter 메소드도 만들어 준다. (실제 메소드 정의는 자동으로 생성된다.)
Room 클래스의 인스턴스는 하나만 있으면 되므로 Singleton 패턴을 사용한다.
그리고 Room 클래스의 객체 생성은 Room.databaseBuilder()를 이용한다.
@Database(entities = [Student::class], exportSchema = false, version = 1) abstract class MyDatabase : RoomDatabase() { abstract fun getMyDao() : MyDAO companion object { private var INSTANCE: MyDatabase? = null private val MIGRATION_1_2 = object : Migration(1, 2) { override fun migrate(database: SupportSQLiteDatabase) { } } fun getDatabase(context: Context) : MyDatabase { if (INSTANCE == null) { INSTANCE = Room.databaseBuilder( context, MyDatabase::class.java, "school_database") .addMigrations(MIGRATION_1_2) .build() // for in-memory database /*INSTANCE = Room.inMemoryDatabaseBuilder( context, MyDatabase::class.java ).build()*/ } return INSTANCE as MyDatabase } } }
이 과정이 끝나면 MainActivity에서 RoomDatabase객체에서 DAO 객체를 받아오고,
이 DAO객체의 메소드를 호출하여 데이터베이스에 접근할 수 있다.
class MainActivity : AppCompatActivity() { private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) } private lateinit var myDao: MyDAO override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) myDao = MyDatabase.getDatabase(this).getMyDao() observeStudents() setListeners() } private fun observeStudents() { myDao.getAllStudents().observe(this) { students -> val str = buildString { students.forEach { student -> append("${student.id}-${student.name}\n") } } binding.textStudentList.text = str } } private fun setListeners() { with(binding) { addStudent.setOnClickListener { val id = editStudentId.text.toString().toIntOrNull() ?: 0 val name = editStudentName.text.toString() if (id > 0 && name.isNotEmpty()) { CoroutineScope(Dispatchers.IO).launch { myDao.insertStudent(Student(id, name)) } } editStudentId.text = null editStudentName.text = null } queryStudent.setOnClickListener { val name = editStudentName.text.toString() CoroutineScope(Dispatchers.IO).launch { val results = myDao.getStudentByName(name) withContext(Dispatchers.Main) { textQueryStudent.text = buildString { results.forEach { student -> append("${student.id}-${student.name}\n") } } } } } deleteStudent.setOnClickListener { val id = editStudentId.text.toString().toIntOrNull() ?: 0 val name = editStudentName.text.toString() if (id > 0) { CoroutineScope(Dispatchers.IO).launch { myDao.deleteStudent(Student(id, name)) } } editStudentId.text = null editStudentName.text = null } } } }
📝 공부한 Kotlin 정리
1) Room
- SQLite를 쉽게 사용할 수 있는 데이터베이스 객체 매핑 라이브러리
- 쉽게 Query를 사용할 수 있는 API를 제공
- Query를 컴파일 시간에 검증
- Query결과를 LiveData로하여 데이터베이스가 변경될 때마다 쉽게 UI를 변경 가능
- SQLite 보다 Room을 사용할 것을 권장
2) Room 주요 3요소
- @Database: 클래스를 데이터베이스로 지정하는 annotation, RoomDatabase를 상속받은 클래스여야 한다.
(Room.databaseBuilder를 이용하여 인스턴스를 생성) - @Entity: 클래스를 테이블 스키마로 지정하는 annotation
- @Dao: 클래스를 DAO(Data Access Object)로 지정하는 annotation
(기본적인 insert, delete, update SQL은 자동으로 만들어주며, 복잡한 SQL은 직접 만들 수 있다.)
3) DAO
- DAO는 interface나 abstract class로 정의되어야 한다.
- Annotation에 SQL 쿼리를 정의하고 그 쿼리를 위한 메소드를 선언한다.
- 가능한 annotation으로 @Insert, @Update, @Delete, @Query가 있다.
- @Insert, @Update, @Delete는 SQL 쿼리를 작성하지 않아도 컴파일러가 자동으로 생성한다.
- @Insert나 @Update는 key가 중복되는 경우 처리를 위해 onConflict를 지정할 수 있다.
- OnConflictStrategy.ABORT: key 충돌시 종료
- OnConflictStrategy.IGNORE: key 충돌 무시
- OnConflictStrategy.REPLACE: key 충돌시 새로운 데이터로 변경
- @Update나 @Delete는 primary key에 해당되는 튜플을 찾아서 변경/삭제한다.
4) Room Database의 주요 어노테이션(Annotations)
- @Database
- 데이터베이스 클래스를 정의할 때 사용한다.
- 데이터베이스에 포함될 엔티티와 버전을 명시한다.
- @Entity
- 데이터베이스 내의 테이블을 정의할 때 사용한다.
- 클래스 이름이 테이블 이름으로 사용되며, 필드는 컬럼으로 매핑된다.
- @PrimaryKey
- 엔티티의 기본 키(primary key)를 정의할 때 사용한다.
- 유니크한 값이어야 하며, 데이터베이스 내에서 각 엔티티를 구분하는 데 사용된다.
- @ColumnInfo
- 테이블의 컬럼 정보를 세부적으로 정의할 때 사용한다.
- 컬럼의 이름, 타입, 인덱스 등을 설정할 수 있다.
- @Dao
- 데이터 접근 객체(Data Access Object)를 정의할 때 사용한다.
- 데이터베이스의 CRUD(Create, Read, Update, Delete) 연산을 위한 메소드를 포함한다.
- @Insert
- 데이터를 삽입하는 메소드에 사용한다.
- 해당 메소드는 엔티티를 인자로 받아 데이터베이스에 추가한다.
- @Query
- 복잡한 SQL 쿼리를 실행하는 메소드에 사용한다.
- 메소드에 주어진 SQL 쿼리를 실행하여 결과를 반환한다.
- @Update
- 데이터를 업데이트하는 메소드에 사용한다.
- 인자로 받은 엔티티의 데이터로 기존 레코드를 갱신한다.
- @Delete
- 데이터를 삭제하는 메소드에 사용한다.
- 인자로 받은 엔티티를 데이터베이스에서 제거한다.
- @Transaction
- 메소드가 하나의 트랜잭션으로 실행되어야 함을 나타낸다.
- 여러 연산을 하나의 작업으로 묶어 실행할 때 사용한다.
- @ForeignKey
- 엔티티 간의 외래 키 관계를 정의할 때 사용한다.
- 참조 무결성을 유지하는 데 도움을 준다.
- @Index
- 특정 컬럼에 인덱스를 생성할 때 사용한다.
- 쿼리 성능을 향상하는 데 유용하다.
'안드로이드' 카테고리의 다른 글
[Android] Retrofit 알아보기 (REST API/ JSON/ GSON) (1) 2024.05.01 [Android] Google Map API 사용하기 (+ SHA 인증서 지문 얻기) (0) 2024.04.30 [Android] 데이터 저장 - SharedPreferences (0) 2024.04.30 [Android] 주소록 App 만들기 - 마무리 & KPT 회고 (0) 2024.04.30 [Android] Check your module classpath for missing or conflicting dependencies (Gradle 인식 안되는 오류) 해결 (0) 2024.04.29