이번에는 Room을 사용하여 DB 연결을 해보겠습니다.
*ChatGPT의 도움을 받아 개발하였습니다.*
Room 연결
Room 정의
Android 공식 ORM(Object-Relational Mapping), SQLite 기반 DB
- 구성요소: Entity(데이터 클래스), DAO(CRUD 쿼리 정의), Database(DB 인스턴스)
- 특징
- DB는 각 디바이스에 로컬로 생성
- 즉, Room이 디바이스 내 앱 전용 저장소에 자동으로 SQLite 기반 DB 파일(.db)을 생성하고 저장
- Room은 외부 앱에서 접근 불가능한 보안된 공간에 저장됨 -> 안전성 높음
- 단, 앱을 삭제하면 내부 저장소에 있던 DB도 같이 삭제됨
- 오프라인 저장 중심 앱에서 대부분 사용
- 설치 방법
- build.gradle 확인
1) dependencies 블록 안에 추가
dependencies {
--------------JAVA--------------
implementation "androidx.room:room-runtime:2.6.1"
annotationProcessor "androidx.room:room-compiler:2.6.1" // Java는 annotationProcessor 사용
implementation "androidx.lifecycle:lifecycle-viewmodel:2.7.0" // ViewModel
implementation "androidx.lifecycle:lifecycle-livedata:2.7.0" // LiveData
--------------Kotlin--------------
// Room (Java용)
implementation("androidx.room:room-runtime:2.6.1")
annotationProcessor("androidx.room:room-compiler:2.6.1")
implementation("androidx.lifecycle:lifecycle-viewmodel:2.7.0") // ViewModel
implementation("androidx.lifecycle:lifecycle-livedata:2.7.0") // LiveData
}
2) android 버전 확인 (java 8 미만일 경우에만 추가)
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
build.gardle.kts 코드 수정 및 버전 확인
...
android {
...
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
dependencies {
...
// Room (Java용)
implementation("androidx.room:room-runtime:2.6.1")
annotationProcessor("androidx.room:room-compiler:2.6.1")
// ViewModel + LiveData
implementation("androidx.lifecycle:lifecycle-viewmodel:2.7.0")
implementation("androidx.lifecycle:lifecycle-livedata:2.7.0")
}
Todo.java(Entity) 코드 수정
...
@Entity(tableName = "todo_table")
public class Todo {
@PrimaryKey(autoGenerate = true) // 기본키, 자동 증가
private int id;
private String content;
private boolean isDone;
// 생성자
public Todo(String content, boolean isDone) {
this.content = content;
this.isDone = isDone;
}
// getter, setter
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public boolean isDone() { return isDone; }
public void setDone(boolean done) { isDone = done; }
}
TodoDAO.java 코드
package com.example.todolist.data;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import androidx.room.Delete;
import java.util.List;
@Dao
public interface TodoDAO {
@Insert
void insert(Todo todo);
@Update
void update(Todo todo);
@Delete
void delete(Todo todo);
@Query("SELECT * FROM todo_table ORDER BY id DESC")
LiveData<List<Todo>> getAllTodos(); // 실시간으로 변경되는 데이터 감지
}
TodoDatabase.java 코드
package com.example.todolist.data;
import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
@Database(entities = {Todo.class}, version = 1) // Entity 설정
public abstract class TodoDatabase extends RoomDatabase {
private static TodoDatabase instance;
public abstract TodoDAO todoDAO();
public static synchronized TodoDatabase getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(context.getApplicationContext(),
TodoDatabase.class, "todo_database")
.fallbackToDestructiveMigration() // 버전 바뀌었을 때 DB 날리고 다시 생성 (초기 개발 단계에서 사용)
.build();
}
return instance;
}
}
TodoRepository.java 코드
package com.example.todolist.repository;
import android.app.Application;
import android.util.Log;
import androidx.lifecycle.LiveData;
import com.example.todolist.data.Todo;
import com.example.todolist.data.TodoDAO;
import com.example.todolist.data.TodoDatabase;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// ViewModel과 DB 사이의 중간 다리 역할
public class TodoRepository {
private TodoDAO todoDao;
private LiveData<List<Todo>> allTodos;
// 백그라운드에서 실행할 Executor
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
public TodoRepository(Application application) {
// DB 인스턴스 생성
TodoDatabase db = TodoDatabase.getInstance(application);
todoDao = db.todoDAO();
allTodos = todoDao.getAllTodos();
}
public LiveData<List<Todo>> getAllTodos() { return allTodos; }
// 추가
public void insert(Todo todo) { executorService.execute(() -> todoDao.insert(todo)); }
// 수정
public void update(Todo todo) { executorService.execute(() -> todoDao.update(todo)); }
// 삭제
public void delete(Todo todo) { executorService.execute(() -> todoDao.delete(todo)); }
}
실제 데이터 확인
현재 기기에 저장된 DB 확인 방법
1. Device File Explorer (Android Studio) 사용하여 db 파일 추출 후 *전용 프로그램을 통해 확인
2. 앱에서 .db 파일을 Export 하도록 직접 코드 구현 후 *전용 프로그램을 통해 확인
3. ADB 명령어로 추출 후 *전용 프로그램을 통해 확인
*전용 프로그램: DB Browser for SQLite, VSCode SQLite 플러그인, Android Studio Database Inspector (API 26+) 등
3개 전부 다 해봤는데 오류가 나지 않았던 2번+DB Browser for SQLite로 확인했습니다.
- 1번 실패 이유: 아래 사진과 같이 Error가 뜸
- 3번 실패 이유: 권한 문제로 .db 파일 복사 불가능
2번 방법 (.db 파일을 외부에 저장할 수 있도록 직접 코드 구현)
1) TodoDatabase.java에 WAL 비활성화 코드(.setJournalMode()) 추가
- 비활성화 이유: Room은 기본적으로 WAL 모드를 사용하는데, WAL 모드를 쓰면 .db 파일뿐만 아니라 -wal, -shm 파일까지 다 복사하거나 Room의 커밋 타이밍을 명시적으로 제어해야 함 -> 테스트나 디버깅이 복잡해짐
...
public abstract class TodoDatabase extends RoomDatabase {
...
instance = Room.databaseBuilder(context.getApplicationContext(),
TodoDatabase.class, "todo_database")
// WAL 비활성화 (빠른 확인용, 개발 시에만 적용, 실제 배포할 때에는 WAL 활성화하기)
.setJournalMode(RoomDatabase.JournalMode.TRUNCATE) // 추가
...
}
2) activity_main.xml에 DB 내보내기 버튼 추가 (+ 레이아웃 조정)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
...
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="40dp" // margin 수정
>
...
<Button // 추가
android:id="@+id/buttonExport"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="DB 내보내기"/>
</LinearLayout>
...
</LinearLayout>
3) MainActivity.java에 exportDatabase 함수 추가
...
public class MainActivity extends AppCompatActivity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// DB 내보내기 버튼 클릭 시 실행
Button btnExport = findViewById(R.id.buttonExport); // 추가
btnExport.setOnClickListener(v -> exportDatabase(MainActivity.this)); // 추가
}
// 추가
// DB 데이터 내보내는 메소드
public void exportDatabase(Context context) {
File dbFile = context.getDatabasePath("todo_database"); // ← 실제 DB 이름
File exportDir = context.getExternalFilesDir(null); // 앱 외부 저장소
if (exportDir != null && !exportDir.exists()) {
exportDir.mkdirs();
}
File outFile = new File(exportDir, "todo_database_copy.db");
try (FileChannel src = new FileInputStream(dbFile).getChannel();
FileChannel dst = new FileOutputStream(outFile).getChannel()) {
dst.transferFrom(src, 0, src.size());
Log.d("EXPORT", "DB exported to: " + outFile.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
}
}
4) .db 파일을 찾아 전용 프로그램을 통해 확인
- 파일 경로: 각자 테스트한 기기 or 에뮬레이터 폴더 -> Android/data/com.example.todolist/files/todo_database_copy.db
★★ 테스트가 끝나고 배포 직전에 전부 삭제하기 ★★
나중에는 테스트 자동화로 바꿔볼 계획입니다.
다음 글에서는 삭제, 수정 기능을 추가하겠습니다.
(삭제 기능은 이미 개발한 상태라 실제 데이터 확인 예시 사진에 버튼이 있어요ㅎ.ㅎ
글이 너무 길어져서 수정 기능까지 넣고 같이 써볼게요)
'Project > Android TodoList' 카테고리의 다른 글
[Android] TodoList 앱 만들기 #03 - UI 구성, Entity 생성, Adapter & MainActivity 연결 (1) | 2025.06.27 |
---|---|
[Android] TodoList 앱 만들기 #02 - 아키텍처 설계 (1) | 2025.06.23 |
[Android] TodoList 앱 만들기 #01 - 기획 (0) | 2025.06.19 |