요약
| 항목 | 내용 |
|---|---|
execSQL(sql, args): Unit | 원시 C-UD |
rawQuery(sql, args): Cursor | 원시 R |
insert(), query(), update(), delete() | 고급 CRUD |
onCreate() | 최초 1회 |
onUpgrade() | DB 버전 변경 |
readableDatabase | 읽기 모드 DB 접근 |
writableDatabase | 쓰기 모드 DB 접근 |
- 커서 초기 위치는 Before First (인덱스 -1);
moveToNext()(없으면 false 반환)
1. 데이터베이스에 파일 저장 (SQLite)
1.1. 안드로이드 데이터베이스 개요
- 안드로이드는 오픈소스 SQLite를 DBMS로 탑재
- 모바일용 경량화 데이터베이스
- 앱의 저장소에 파일 형태로 저장 → 외부 앱 접근 불가
- 데이터의 CRUD 작업: Create(insert), Read(select), Update, Delete
1.2. SQLiteDatabase 객체 생성
val db = openOrCreateDatabase("testdb", Context.MODE_PRIVATE, null)| 인자 | 설명 |
|---|---|
| 첫 번째 | DB 이름 (없으면 새로 생성) |
| 두 번째 | Context.MODE_PRIVATE → 현재 앱에서만 접근 가능 |
| 세 번째 | 커서 팩토리 (null = 기본 커서 사용) |
커서(Cursor): 쿼리 결과를 저장하는 객체로, 데이터베이스 조회 시 결과값을 담음
1.3. SQL 실행 함수
| 함수 | 용도 |
|---|---|
execSQL(sql, args): Unit | 테이블 생성, 삽입, 삭제, 수정 |
rawQuery(sql, args): Cursor | 데이터 조회 |
// 테이블 생성
db.execSQL("
create table USER_TB(
_id INTEGER primary key autoincrement,
name TEXT not null,
phone TEXT
)
")
// 데이터 삽입
db.execSQL(
"insert into USER_TB(name, phone) values (?,?)",
arrayOf<String>("kkang","0101111")
)
// 데이터 조회
val cursor = db.rawQuery("select * from USER_TB", null)1.4. Cursor 사용법
(1) 행(Row) 이동 함수
| 함수 | 설명 |
|---|---|
moveToFirst() | 첫 번째 행 선택 |
moveToLast() | 마지막 행 선택 |
moveToNext() | 다음 행 선택 (없으면 false 반환) |
moveToPrevious() | 이전 행 선택 |
moveToPosition(n) | n번째 위치의 행 선택 |
- 커서 초기 위치
- Before First (인덱스 -1; 데이터 시작 바로 직전)
(2) 열(Column) 데이터 가져오기
while (cursor.moveToNext()) {
val name = cursor.getString(0) // 첫 번째 열
val phone = cursor.getString(1) // 두 번째 열
}| 함수 | 타입 |
|---|---|
getString(index) | 문자열 |
getInt(index) | 정수 |
getDouble(index) | 실수 |
1.5. 데이터 다루는 함수 (CRUD)
- 원시 SQL문 대신 안드로이드에서 제공하는 메서드를 사용할 수도 있다.
(1) INSERT
insert(): 테이블에 데이터 추가- (ContentValues 객체에 컬럼-값 쌍으로 저장하여 전달)`
val values = ContentValues()
values.put("name", "홍길동")
values.put("phone", "010-1234-5678")
val newRowId = db.insert("USER_TB", null, values) // 값이 없는 컬럼에는 null을 저장
// 성공 시 고유 ID 반환, 실패 시 -1 반환(2) UPDATE
update(): 조건에 맞는 데이터 수정
val values = ContentValues()
values.put("phone", "010-9999-8888")
val updatedCount = db.update("USER_TB", values, "name = ?", arrayOf("홍길동"))
// 수정된 ROW 개수 반환(3) DELETE
delete(): 조건에 맞는 데이터 삭제
val deletedCount = db.delete("USER_TB", "name = ? AND phone = ?", arrayOf("홍길동", "010-1234-5678"))
// 삭제된 ROW 개수 반환(4) SELECT
query(): 구조화된 SELECT 쿼리 실행`
val cursor = db.query(
"USER_TB", // table
arrayOf("name","phone"), // columns
"phone=?", // selection (where절)
arrayOf("0101112"), // selectionArgs (?에 삽입)
null, // groupBy
null, // having
null // orderBy
)| 매개변수 | 의미 |
|---|---|
table | 조회할 테이블명 |
columns | 가져올 열 이름 배열 |
selection | WHERE 조건식 |
selectionArgs | ?에 들어갈 값 배열 |
groupBy | GROUP BY 절 |
having | HAVING 조건 |
orderBy | ORDER BY 정렬 기준 |
1.6. SQLiteOpenHelper
SQLiteOpenHelper- 테이블 생성, 변경, 제거를 관리하는 추상 클래스
- 추상 클래스이므로 반드시 상속받아 하위 클래스 작성 필요
| 메서드 | 호출 시점 | 용도 |
|---|---|---|
onCreate() | 앱 설치 후 최초 1회 | 테이블 생성 |
onUpgrade() | DB 버전 변경 시마다 | 테이블 스키마 변경 |
class DBHelper(context: Context) : SQLiteOpenHelper(context, "testdb", null, 1) {
override fun onCreate(db: SQLiteDatabase?) {
db!!.execSQL("CREATE TABLE groupTBL (gName CHAR(20) PRIMARY KEY, gNumber INTEGER);")
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
db!!.execSQL("DROP TABLE IF EXISTS groupTBL")
onCreate(db)
}
}-
onCreate(db)- 앱 설치 후 DB가 처음 생성될 때 1회 호출됨
- 주로 테이블 생성(
CREATE TABLE) 코드를 작성
-
onUpgrade(db, old, new)- DB 버전 번호가 변경될 때 호출됨
- 주로 기존 테이블 삭제(
DROP) 후 재생성 등 스키마 변경 코드를 작성
// 쓰기 모드로 DB 객체 생성
val db: SQLiteDatabase = DBHelper(this).writableDatabase
// 읽기 모드로 DB 객체 생성
val db: SQLiteDatabase = DBHelper(this).readableDatabase2. 실습: 가수 그룹 관리 DB 구축
SQLiteOpenHelper를 활용한 간단한 CRUD 앱 실습
-
UI 구성
LinearLayout과layout_weight를 활용해 화면 분할
-
DB Helper 작성
myDBHelper클래스를 만들어onCreate에 그룹 테이블(gName, gNumber) 생성 코드 작성
2.1. 실습 주요 코드
// 초기화 버튼
binding.btnInit.setOnClickListener {
sqlDB = myHelper.writableDatabase
myHelper.onUpgrade(sqlDB, 1, 2)
sqlDB.close()
}
// 입력 버튼
binding.btnInsert.setOnClickListener {
sqlDB = myHelper.writableDatabase
sqlDB.execSQL("INSERT INTO groupTBL VALUES ('${edtName.text}', ${edtNumber.text});")
sqlDB.close()
}
// 조회 버튼
binding.btnSelect.setOnClickListener {
sqlDB = myHelper.readableDatabase
val cursor = sqlDB.rawQuery("SELECT * FROM groupTBL;", null)
while (cursor.moveToNext()) {
val name = cursor.getString(0)
val number = cursor.getString(1)
}
cursor.close()
sqlDB.close()
}2.2. 연습문제 힌트 (수정/삭제)
- 입력/수정/삭제 후 즉시 결과 표시 →
btnSelect.callOnClick()호출
-- 수정 SQL
UPDATE groupTBL SET gNumber = 변경인원 WHERE gName = "그룹이름";
-- 삭제 SQL
DELETE FROM groupTBL WHERE gName = "그룹이름";3. 파일에 보관하기
- 안드로이드 앱에서 파일 입출력은
- 표준 Java I/O (
java.io.*- File, Streams, Readers/Writers)를 동일하게 사용한다. - 저장소는 크게 세 가지로 나뉜다.
- 표준 Java I/O (
| 저장소 | 특징 |
|---|---|
| 내장 메모리 앱별 저장소 | 앱 삭제 시 함께 삭제, 외부 접근 불가 |
| 외장 메모리 앱별 저장소 | 앱 삭제 시 함께 삭제 |
| 외장 메모리 공용 저장소 | 앱 삭제 후에도 파일 유지, 모든 앱이 접근 가능 |
3.1. 내장 메모리 (Internal Storage)
-
앱의 패키지명으로 생성되는 전용 디렉터리
-
앱이 삭제되면 파일도 함께 삭제된다.
-
용량이 작다.
-
사용법
openFileOutput("파일명", 모드),openFileInput("파일명")함수를 통해- 스트림을 열어 읽고 쓴다.
// 쓰기
binding.btnWrite.setOnClickListener {
val outFs = openFileOutput("file.txt", Context.MODE_PRIVATE)
outFs.write("모바일 프로그래밍".toByteArray())
outFs.close()
}
// 읽기
binding.btnRead.setOnClickListener {
try {
val inFs = openFileInput("file.txt")
val txt = ByteArray(30)
inFs.read(txt)
val str = txt.toString(Charsets.UTF_8) // 한글 깨짐 방지
inFs.close()
} catch (e: IOException) {
Toast.makeText(applicationContext, "파일 없음", Toast.LENGTH_SHORT).show()
}
}3.2. 외장 메모리 앱별 저장소 (External Storage - App-specific)
- 외장 메모리 중 해당 앱에만 할당된 개별 공간이다. 앱 삭제 시 같이 삭제된다.
(1) 외장 메모리 사용 가능 여부 확인
if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
// 외장 메모리 사용 가능
} else {
// 외장 메모리 사용 불가
}(2) 사용법
getExternalFilesDir(null)함수로 경로(File 객체)를 얻어와 사용한다.- 외부 앱에서 접근하려면 파일 프로바이더(FileProvider) 설정이 필요하다.
val file: File? = getExternalFilesDir(null) // 앱 기본 디렉토리
// getExternalFilesDir() 매개변수 종류
Environment.DIRECTORY_PICTURES // 사진
Environment.DIRECTORY_DOCUMENTS // 문서
Environment.DIRECTORY_MUSIC // 음악
Environment.DIRECTORY_MOVIES // 동영상3.3. 외장 메모리 공용 저장소 (External Storage - Public)
- 사진, 음악, 동영상 등 모든 앱이 공유할 수 있는 공간이다.
- 앱을 삭제해도 파일은 유지된다.
- 직접 경로로 접근하지 않고 **
MediaStoreAPI(ContentResolver)**를 통해 접근해야 한다.
// 외장 메모리 파일 쓰기
val file = File(getExternalFilesDir(null), "test.txt")
val writeStream = file.writer()
writeStream.write("hello world")
writeStream.flush()
// 외장 메모리 파일 읽기
val readStream = file.reader().buffered()
readStream.forEachLine { Log.d("tag", "$it") }(1) 공용 저장소 권한 설정
| Android 버전 | API Level | 필요 권한 |
|---|---|---|
| Android 12 이전 | ~ 32 | READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE |
| Android 13 | 33 | READ_MEDIA_IMAGES, READ_MEDIA_VIDEO |
| Android 14 이후 | 34+ | READ_MEDIA_VISUAL_USER_SELECTED 추가 |
<!-- Manifest 권한 선언 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED"/>// 버전별 권한 요청 코드
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // API 34+
requestPermissions.launch(arrayOf(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_VISUAL_USER_SELECTED))
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // API 33
requestPermissions.launch(arrayOf(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO))
} else {
requestPermissions.launch(arrayOf(READ_EXTERNAL_STORAGE))
}3.4. 실습: 파일 쓰고 읽기
-
파일 쓰기: 내장 메모리에
openFileOutput으로 파일을 열고, 문자열을toByteArray()로 바이트 단위로 변환하여write()로 저장 -
파일 읽기:
openFileInput으로 읽어온 바이트 배열을toString(Charsets.UTF_8)을 사용해 한글 깨짐 없이 문자열로 복원하여 화면에 출력