이번 영상에서는 SQLite와 Room에 대해 알아보도록 하겠습니다.
여러 사람들의 연락처 데이터는 다음과 같이 테이블 구조로 관리할 수 있습니다. 이런 식으로 통일된 규칙의 데이터를 관리할 수 있는 테이블을 데이터베이스라고 합니다.
| 순서 | 이름 | 전화번호 | 이메일 | 주소 |
|---|---|---|---|---|
| 0 | 김철수 | 010-1111-1111 | kim@abc.com | 서울 |
| 1 | 이영희 | 010-2222-2222 | lee@abc.com | 서울 |
| 2 | 박민수 | 010-3333-3333 | park@abc.com | 인천 |
이 때 1행 2열의 이름을 Key라고 하고 2행 2열의 김철수를 Value라고 합니다. 전화번호, 이메일, 주소도 역시 Key이고 그에 대응되는 값들도 역시 Value가 되겠죠. 순서라는 Key는 각 데이터에 대해 겹치지 않는 고유한 값이기 때문에 고유키(Primary Key)라고 합니다.
이렇게 Key와 Value가 1:1이나 1:n, 또는 n:n 의 관계를 가지는 데이터베이스를 관계형 데이터베이스(Relational Database)라고 합니다.
위에 보여드린 DB에서 박민수의 전화번호를 찾는건 매우 간단합니다. 하지만 만약 행의 길이가 수만줄에 이르고 그 안에 박민수의 동명이인도 수백명이 존재하는 테이블이 있다면 어떨까요? 그 안에서 내가 원하는 박민수의 전화번호를 찾는건 쉽지 않은 일일겁니다. Structured Query Language (SQL)는 그런 복잡한 DB를 좀 더 용이하게 다루기 위해서 만들어진 언어입니다.
CRUD라는 표현을 들어보신적 있으실 겁니다. CRUD는 기본적인 데이터 처리 기능인 Create(생성), Read(읽기), Update(갱신), Delete(삭제)를 의미하는 말로, 데이터베이스를 다루기 위해 가장 기본이 되는 4가지의 명령을 묶어놓은 단어입니다. SQL은 다음과 같은 INSERT,SELECT,UPDATE,DELETE 명령어를 통해 CRUD를 구현할 수 있습니다.
| 이름 | 조작 | SQL |
|---|---|---|
| Create | 생성 | INSERT |
| Read(또는 Retrieve) | 읽기(또는 인출) | SELECT |
| Update | 갱신 | UPDATE |
| Delete(또는 Destroy) | 삭제(또는 파괴) | DELETE |
SQL에서 실제 CRUD는 다음과 같이 구현하게 되는데요, 언어의 전체 문법은 SQL syntax 페이지에서 확인할 수 있습니다.
// 작성자가 무명씨, 홍길동인 문서의 모든 항목을 가져옴
SELECT * FROM document WHERE author like '김*';
// table에 field1=value1, field2=value2, ...와 같은 속성값을 가지는 항목을 새로 생성하여 삽입
INSERT INTO table(field1, field2, ...) VALUES (value1, value2, ...);
// 데이터를 수정하는 구문으로 table의 field1에 value1, field2에 value2, ... 로 변경
UPDATE table SET field1=value1, field2=value2, {WHERE 조건};
// 데이터를 삭제
DELETE FROM table {WHERE 조건};
// 데이터베이스 내의 모든 테이블, 스키마, 관계(Relation)를 전부 삭제
DROP DATABASE database_name;
대량의 데이터를 다루는데 관계형 데이터베이스가 사용되고, SQL이라는 언어를 사용해 데이터베이스의 데이터를 다룬다는 걸 알았습니다. 이런 데이터베이스와 SQL을 결합해서, 사용할 수 있는 형태의 프로그램으로 만든 것을 Database Management System이라고 합니다. 거기에 관계형 데이터베이스를 사용했다면 RDBMS가 되겠죠.
여러분이 잘 아시는 Oracle이나 MySQL 등이 대표적인 DBMS입니다. DB-Engines Ranking에 따르면 Oracle, MySQL, Microsoft SQL Server가 현재 시장에서 제일 인기있는 DBMS라고 하네요.


그렇게 많고많은 DBMS중에 SQLite라는 것이 있습니다. 표준SQL을 지원하는 DB를 파일 하나로 구현한 경량 DBMS인데요, 운용시 리소스사용이 적고 무료로 사용할 수 있는 오픈소스이기 때문에 Android와 iOS에서 DB를 구현하기 위한 기본 라이브러리로 채택되어 있습니다.
처음엔 미해군의 구축함에서 이용하기 위해 만들어진 프로그램인데 공식 홈페이지에 따르면 현재는 전세계에서 1조개가 넘는 SQLite가 운용되고 있다고 하네요. SQLite에 대해 더 알고싶으신 분은 SQLite의 알려지지 않은 이야기를 읽어보시면 좋을 것 같습니다.
상기한 이유로 안드로이드 API에는 SQLite가 기본 내장되어 있습니다. 복잡한 DB를 다룰 계획이 없다면 SQLite를 이용해 DB를 구축하는 것으로 충분합니다.
현재까지 개발된 SQLite의 최신 버전은 3.38.1 (2022-03-12)인데 Android API에 내장되는 SQLite는 안정성을 고려해 다음 표와 같이 좀 더 낮은 버전이 내장되어 있습니다.
| Android API | SQLite Version |
|---|---|
| API 31 | 3.32 |
| API 30 | 3.28 |
| API 28 | 3.22 |
| API 27 | 3.19 |
| API 26 | 3.18 |
SQLite를 사용하기 위해서는 DB를 만들고 그 DB에 대해 SQL query를 전달하여 CRUD를 수행하면 됩니다. 안드로이드에서는 android.database.sqlite 패키지를 이용해, 작성한 데이터베이스에 대해 CRUD를 수행할 수 있는데요, android.database.sqlite 패키지에는 다음과 같은 클래스들이 있습니다.
| Classes | Description |
|---|---|
| SQLiteClosable | An object created from a SQLiteDatabase that can be closed. |
| SQLiteCursor | A Cursor implementation that exposes results from a query on a SQLiteDatabase. |
| SQLiteDatabase | Exposes methods to manage a SQLite database. |
| SQLiteOpenHelper | A helper class to manage database creation and version management. |
| SQLiteProgram | A base class for compiled SQLite programs. |
| SQLiteQuery | Represents a query that reads the resulting rows into a SQLiteQuery. |
| SQLiteQueryBuilder | This is a convenience class that helps build SQL queries to be sent to SQLiteDatabase objects. |
| SQLiteStatement | Represents a statement that can be executed against a database. |
그런데 구글 공식문서인 Save data using SQLite에서는 다음과 같은 이유로 이 작업을 직접 수행하는것을 권하지 않고 있습니다. 원시 SQL 쿼리를 컴파일 타임에 체크할 수 없어 오류가 쉽게 발생할 수 있고, 쿼리와 데이터 객체간의 변환에 많은 상용구 코드를 사용해야 한다는 문제가 있다고 합니다.
There is no compile-time verification of raw SQL queries. As your data graph changes, you need to update the affected SQL queries manually. This process can be time consuming and error prone.
You need to use lots of boilerplate code to convert between SQL queries and data objects.
구글에서는 안드로이드에서 SQLite를 다루기 위해 android.database.sqlite 패키지를 만들었습니다. 그런데 이 패키지를 이용하면 DB를 직접 열고 닫아야 한다거나, SQL 쿼리를 직접 사용해야하는 등 SQLite 레이어를 추상화하는데는 아직 부족한 부분이 많았습니다. 그래서 구글에서는 SQLite를 더 안전하게 다루기 위해 Room이라는 라이브러리를 만듭니다.
Room은 다음과 같은 구조를 가지고 있습니다. Entities, Data Access Objects, Room Database로 구성되는데 DB의 각 항목은 Entities로 만들어 취급하고, DB에는 DAO를 통해 접근하게 되어 있습니다.

Room은 DB 내부의 각 항목을 프로그램에서 사용하기 위해 Object Relational Mapping(ORM)이라는 과정을 통해 객체로 변환합니다. 이 변환을 위해 다음과 같이 Entity를 정의하여 사용합니다.
@Entity
data class User(
@PrimaryKey val uid: Int,
@ColumnInfo(name = "first_name") val firstName: String?,
@ColumnInfo(name = "last_name") val lastName: String?
)
DB는 쿼리를 가능한 사용하지 않고 DAO(Data Access Objects)를 써서 간접적으로 조작합니다.
@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)
}
그리고 다음과 같은 RoomDatabase 객체를 만들어서 Entity에 대한 DAO를 이 객체가 주관하도록 합니다.
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
이런 구조를 구축함으로써 사용자가 DB 파일에 직접 접근하지 않고 SQL 쿼리사용을 최소화하면서 조작 자체를 추상화할 수 있게 되지요. 따라서 결과적으로 SQLite를 더 안전하게 다룰 수 있게 되는 것입니다. 위험하게 작동할 수 있는 패키지를 직접 사용하지 말고 우리가 안전하게 쓸수있게 만들어 둔 래퍼 라이브러리를 사용하라... Fragment를 직접 다루지 말고 Navigation을 사용하도록 한 접근과 비슷하지요.
구글이 설명하는 Room의 장점은 다음과 같다고 하는데, 지금까지 설명한 구조를 구현하였기 때문에 이러한 장점이 얻어진 것입니다.
이렇게 해서 SQlite와 Room에 대해 알아보았습니다.