-
[Android] SQLite, SQLiteOpenHelper, Local DB ์ดํดAndroid 2021. 11. 30. 09:46๋ฐ์ํ
SQLite๋?
Android ๊ฐ๋ฐ์ ํ๋ฉด์ ์ฑ์ ์ฌ์ฉํ๊ณ ์ข ๋ฃํ๋๋ผ๋ ๋ฐ์ดํฐ๋ฅผ ๊ณ์ ์ ์ฅ๋์ด์ผํ ํ์์ฑ์ด ์์ต๋๋ค. ๊ฐ๋จํ๊ฒ ์ ์ฅํ๋ ๊ฒ์ด๋ผ๋ฉด SharedPreference๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ๋ ์๊ฒ ์ง๋ง, key์ value์ ๊ฐ์ผ๋ก๋ง ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ํํํ๊ธฐ ํ๋ค๊ณ , ๋ง์ ๋ฐ์ดํฐ๋ฅผ ์ฒด๊ณ์ ์ผ๋ก ๊ด๋ฆฌํ๊ธฐ๋ ์ด๋ ต์ต๋๋ค. ๋ฐ๋ผ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํด์ผ ํ๊ณ ์๋๋ก์ด๋์์๋ ๊ฐ๋ฒผ์ด ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ธ SQLite๊ฐ ๋ค์ดํฐ๋ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํฌํจ๋์ด ์์ต๋๋ค. SQLite์ ์ฃผ์ ํน์ง์ ๋ฐ์ดํฐ ์กฐํ๊ฐ ๋น ๋ฅด๊ณ , ํ์ค SQL ์ ์ง์ํ์ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ฃผ์ ๊ธฐ๋ฅ์ธ C(Create), R(Read), U(Update), D(Delete)๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
SQLite๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ๋จํ Todo List๋ฅผ ์์ฑํ๋ ์ดํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค์ด๋ณด๋ ค๊ณ ํฉ๋๋ค. ์ํคํ ์ฒ์ ๋์์ธ ํจํด์ ์ฌ์ฉํ์ง ์๊ณ ๊ฐ๋จํ๊ฒ ๋์๋ง ํ ์ ์๋ ์์ ๋ฅผ ๊ตฌํํด๋ดค์ต๋๋ค.
๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ง๋ค๊ธฐ
๋จผ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๋ง๋ค๊ธฐ ์ํ API๋ ContextWrapper ํด๋์ค์ ์๋ openOrCreateDatabase ํจ์์ ๋๋ค.
DB ํ์ผ ์ด๋ฆ, ํ์ผ ๋ชจ๋, Cursor ๊ฐ์ฒด๋ฅผ ๋ง๋๋ Factory๋ฅผ ๋งค๊ฐ๋ณ์๋ก ๋ฐ์ SQLiteDatabase๋ฅผ ์์ฑํฉ๋๋ค. ์ค์ ๋ก ํด๋น API๋ฅผ ์ฌ์ฉํ๋ฉด name์ผ๋ก ์ง์ ๋ ํ์ผ๋ช ์ db ํ์ผ์ด ์๊ธฐ๊ณ SQLiteDatabas ๊ฐ์ฒด๋ฅผ ์ฐธ์กฐํ์ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ด๊ฑฐ๋ ๋ง๋ค ์ ์์ต๋๋ค.
SQLiteDatabase๊ฐ์ฒด์์ ์ค์ํ ๋ฉ์๋๋ก execSQL๊ฐ ์์ต๋๋ค. execSQL์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๋ง๋ ํ์ ํ์ค SQL๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก ํฉ๋๋ค. ํ์ง๋ง ๋ฆฌํด ๊ฐ์ด void์ด๊ธฐ ๋๋ฌธ์ SELECT ๊ตฌ๋ฌธ๊ณผ ๊ฐ์ ๋ฐํ ๊ฐ์ด ์กด์ฌํ๋ SQL๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
ํ ์ด๋ธ ์์ฑ/์ญ์
โป ์คํค๋ง
ํ ์ด๋ธ ์์ฑํ๊ธฐ์ ์์ ์คํค๋ง ์ฉ์ด์ ๋ํด ์์์ผ ํฉ๋๋ค. ์คํค๋ง๋ ๊ฐ๋จํ๊ฒ๋ ํ ์ด๋ธ์ ๊ตฌ์กฐ๋ฅผ ์ ์ํ ๊ฒ์ด๊ณ ์ฌ์ ์ ์ ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์๋ฃ์ ๊ตฌ์กฐ, ์๋ฃ์ ํํ ๋ฐฉ๋ฒ, ์๋ฃ ๊ฐ์ ๊ด๊ณ๋ฅผ ํ์ ์ธ์ด๋ก ์ ์ํ ๊ตฌ์กฐ๋ผ๊ณ ํฉ๋๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ด์๋ ์ฌ๋ฌ ํ ์ด๋ธ์ด ์กด์ฌํ๊ณ ๊ฐ ํ ์ด๋ธ์๋ ๋ฐ์ดํฐ์ ํ์, ๊ฐฏ์ ,Primary key ๊ฐ ๋ชจ๋ ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ ์ด๋ฅผ ์ ์ํ ๊ฒ์ด ์คํค๋ง๋ผ๊ณ ์๊ฐํ์๋ฉด ๋ฉ๋๋ค.
์๋๋ก์ด๋ ๊ณต์๋ฌธ์์์๋ ์คํค๋ง๋ฅผ ์ ์์ฑํ๊ธฐ ์ํ ๋ฐฉ๋ฒ์ผ๋ก Contract ํด๋์ค๋ฅผ ์์ฑํ๊ณ BaseColumns ์ธํฐํ์ด์ค ๊ตฌํ์ฒด๋ฅผ ๋ง๋ค์ด ๊ธฐ๋ณธ ํค ํ๋๋ฅผ ์์๋ฐ๋ ๊ฒ์ ๊ฐ์ด๋ํ๊ณ ์์ต๋๋ค.
object TodoContract { const val DATABASE_NAME = "Todo.db" object TodoEntry : BaseColumns { const val TABLE_NAME = "TODO" const val COLUMN_TITLE = "title" const val COLUMN_DESCRIPTION = "description" const val COLUMN_DATE = "date" const val CREATE_QUERY = "CREATE TABLE IF NOT EXISTS $TABLE_NAME (" + "$_ID INTEGER PRIMARY KEY AUTOINCREMENT, " + "$COLUMN_TITLE TEXT, " + "$COLUMN_DESCRIPTION TEXT, " + "$COLUMN_DATE TEXT)" const val DROP_QUERY = "DROP TABLE IF EXISTS $TABLE_NAME" } }
TodoContract ์ค๋ธ์ ํธ๋ฅผ ์์ฑํ๊ณ TodoEntry๋ ํ๋์ ํ ์ด๋ธ ์ ์ํ๋๋ฐ ํ์ํ ์์๋ค์ ์ ์ธํ๊ณ ์์ต๋๋ค. ์ด์ฒ๋ผ ์คํค๋ง๋ฅผ ์์ฑํ ๋๋ ์ ์ฒด ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ ์ด์์ ์ญํ ์ ํ๋ Contract ์ค๋ธ์ ํธ๋ฅผ ์์ฑํ๊ณ ๊ฐ ํ ์ด๋ธ๋ง๋ค Entry ์ค๋ธ์ ํธ๋ฅผ ์์ฑํ์ฌ ๋์ผ ํจํค์ง์ ๋ชจ๋ ํด๋์ค์์ ์ฌ์ฉํ ์ ์๋๋ก ํฉ๋๋ค.
private fun createTable() { Thread { db.execSQL(CREATE_QUERY) }.start() }
TodoEntry์ ํ ์ด๋ธ์ ์์ฑํ๋ SQL๋ฌธ์ ์ ์ธํ๊ณ ์๋ก์ด ์ค๋ ๋๋ฅผ ๋ง๋ค์ด์ execSQL ๋ฉ์๋๋ฅผ ์ด์ฉํ๋ฉด ์คํ์ํฌ ์ ์์ต๋๋ค. ๊ฐ๋จํ ๋ช ๋ น์ด๋ ๋ฉ์ธ์ค๋ ๋์์ ์ฟผ๋ฆฌ๋ฅผ ํด๋ ๋ฌธ์ ๋ ์์ง๋ง, ์ผ๋ฐ์ ์ผ๋ก DB ๋ช ๋ น์ ๋ฐฑ๊ทธ๋ผ์ด๋ ์ค๋ ๋์์ ์คํํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค. ํ ์ด๋ธ ์ด๋ฆ์ ๋์๋ฌธ์ ๊ตฌ๋ณ ์๊ณ ๋์ด์ฐ๊ธฐ๋ง ์กฐ์ฌํ์ฌ syntax error๊ฐ ๋ํ๋์ง ์๋๋ก ํฉ๋๋ค.
View - Tool Windows - Devie File Expolorer์ ํตํด db ํ์ผ์ด ์์ฑ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค. /data/data/package_name ํด๋์ ๋ค์ด๊ฐ๋ฉด databases ํด๋๊ฐ ์๊ณ ํด๋น ํด๋์ openOrCreateDatabase ๋ฉ์๋๋ก ์์ฑํ DB ํ์ผ์ด ์์ต๋๋ค. DB ํ์ผ์ ์ฝ๊ธฐ ์ํด์๋ ์ค๋ฅธ์ชฝ ํด๋ฆญ ํ Save as ๋๋ฌ ์ ์ฅํ๊ณ DB Browser for SQLite ํด์ ์ค์นํด์ผ ํฉ๋๋ค.
์ค์น ํ ์คํํ์ฌ ํด๋น DB ํ์ผ์ ์ด๋ฉด ๋ค์๊ณผ ๊ฐ์ด DB ๋ด ํ ์ด๋ธ์ ํ์ธํ ์ ์์ต๋๋ค. ๋ฐ์ดํฐ ๋ณด๊ธฐ ํญ์์๋ ํ ์ด๋ธ ๋ด ์ ์ฅ๋ ๋ฐ์ดํฐ๋ค๋ ํ์ธ์ด ๊ฐ๋ฅํฉ๋๋ค. ์ญ์ ๋ ๋์ผํ๊ฒ DROP TABLE ์ ์ฌ์ฉํ์ฌ execSQL ๋ฉ์๋๋ก ์คํํ๋ฉด ํ ์ด๋ธ ์ญ์ ๊ฐ ๋ฉ๋๋ค.
๋ฐ์ดํฐ ์ถ๊ฐํ๊ธฐ
private fun bindViews() = with(binding) { ... Thread { var sql = "INSERT INTO $TABLE_NAME " + "($COLUMN_TITLE, $COLUMN_DESCRIPTION, $COLUMN_DATE)" + " VALUES " + "('$title', '$description', '$date')" Log.d(TAG, sql) db.execSQL(sql) updateRecyclerView() }.start() ... }
floatingButton์ ํด๋ฆญํ๋ฉด Dialog๊ฐ ๋ํ๋๊ณ 3๊ฐ์ EditText๋ก Title, Description, Date ๋ฌธ์๋ฅผ ์ ๋ ฅ๋ฐ๊ฒ ๊ตฌํ๋์ด ์์ต๋๋ค. ์ดํ ์ ์ฅ์ ํด๋ฆญํ๋ฉด ๋ฐฑ๊ทธ๋ผ์ด๋ ์ค๋ ๋์์ INSERT DB ๋ช ๋ น์ ์คํํ๋๋ก ํ์์ต๋๋ค. INSERT ๊ตฌ๋ฌธ์์ ๋ฐ์ดํฐ์ ํด๋น๋๋ VALUES๋ฅผ ํํํ ๋ ๋ฐ์ดํ๋ก ๊ตฌ๋ณํ์ฌ ๊ฐ ์ปฌ๋ผ์ ๋ฐ์ดํฐ๋ฅผ ๋งค์นญ์์ผ์ค์ผ syntax error๊ฐ ๋ํ๋์ง ์์ต๋๋ค.
DB ํ์ผ์ ์ ์์ ์ผ๋ก ์์ฑ๋๋ ๊ฒ์ ์ ์ ์์ต๋๋ค. primary key์ ํด๋นํ๋ _id ํ๋๋ autoincrement ํด๋์ด 1๋ถํฐ ์์ํ์ฌ ์๋์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ ๋๋ง๋ค 1์ฉ ์ฆ๊ฐํ์ฌ ์ถ๊ฐ๊ฐ ๋ฉ๋๋ค.
๋ฐ์ดํฐ ์ ๋ฐ์ดํธ
private fun updateTodoData(position: Int) { ... Thread { var sql = "UPDATE $TABLE_NAME SET " + "$COLUMN_TITLE = '$title', " + "$COLUMN_DESCRIPTION = '$description', " + "$COLUMN_DATE = '$date' " + "WHERE $_ID = $position" Log.d(TAG, sql) db.execSQL(sql) updateRecyclerView() }.start() ... }
Update๋ ๋์ผํ๊ฒ Dialog๊ฐ ๋ํ๋์ Title, Description, Date๋ฅผ ๋ฐ์์ ๋ณ๊ฒฝํ๋๋ก ํ์์ต๋๋ค. primary key๊ฐ _ID์ด๊ธฐ ๋๋ฌธ์ ์กฐ๊ฑด๋ฌธ์ผ๋ก ๋ณ๊ฒฝํ๊ณ ์ถ์ ID๋ก ํํฐ๋ฅผ ๊ฑธ์ด SQL๋ฌธ์ ์์ฑํฉ๋๋ค. ๊ธฐ์กด์ ๋ฐ์ดํฐ๊ฐ Title = Android, Description = SQLite, Date = 2021-12-02 ๋ก ์ ์ฅ๋์ด ์์ผ๋, SQLite์์ Room์ผ๋ก ๋ณ๊ฒฝํ์ฌ ์ ๋ฐ์ดํธ ํ์์ต๋๋ค.
๋ฐ์ดํฐ ์กฐํํ๊ธฐ
private fun updateRecyclerView() { if(db == null) return todoAdapter.clear() Thread { val list = mutableListOf<Todo>() val sql = "select * from ${TABLE_NAME}" val cursor = db.rawQuery(sql, null) while (cursor.moveToNext()) { list.add( Todo( cursor.getString(1), cursor.getString(2), cursor.getString(3) ) ) } todoAdapter.addAll(list) runOnUiThread { todoAdapter.notifyDataSetChanged() } }.start() }
๋ฐ์ดํฐ๋ฅผ ์กฐํํ๊ธฐ ์ํด์๋ SELECT๋ก ์์ํ๋ SQL๋ฌธ์ ์ฌ์ฉํ๊ฒ ๋๋๋ฐ ์ด์ ์๋ execSQL ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ง๋ง, ๋ฐ์ดํฐ ์กฐํ๋ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์์ผ ํ๊ธฐ ๋๋ฌธ์ rawQuery๋ผ๋ ๋ฉ์๋๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
rawQuery ๋ฉ์๋์ ๋ฆฌํด ๊ฐ์ธ Cursor ๊ตฌํ์ฒด๋ ์ฟผ๋ฆฌ์ ๊ฒฐ๊ณผ์ ์ฝ๊ณ ์ธ ์ ์๋ ์ ๊ทผ ๊ถํ์ ์ ๊ณตํ๋ ์ธํฐํ์ด์ค์ ๋๋ค. ์ฆ, Cursor ๋ฅผ ํตํด์ ์ด๋ฏธ ์ ์ฅ๋ ๋ฐ์ดํฐ๋ค์ ์์๋๋ก ์ ๊ทผํ ์ ์์ผ๋ฉฐ, ์ฒ์์๋ ์๋ฌด๋ฐ ๋ฐ์ดํฐ๋ฅผ ๊ฐ๋ฆฌํค์ง ์๊ณ moveToNext ๋ฉ์๋๋ก ๋ค์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ๋ฆฌํค๋๋ก ํ์ฌ ๊ฐ์ ์ฝ์ ์ ์์ต๋๋ค. ์ ์์์์ moveToNext ๊ฒฐ๊ณผ๊ฐ false ์ ๊น์ง ์คํํ์ฌ Todo list์ ์ถ๊ฐํ๋๋ก ๊ตฌํํ์์ต๋๋ค.
๋ฐ์ดํฐ ์ญ์ ํ๊ธฐ
private fun deleteTodoData(id: Int) { Thread { var sql = "DELETE FROM $TABLE_NAME WHERE _id = ${id}" db?.execSQL(sql) updateRecyclerView() }.start() }
๋ฐ์ดํฐ ์ญ์ ๋ํ DELETE ์ฟผ๋ฆฌ๋ฌธ์ ์ฌ์ฉํ์ฌ WHERE ์ ํด๋นํ๋ ์กฐ๊ฑด์ ์ถฉ์กฑ์ํค๋ ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ ์ ์๋๋ก ํ ์ ์์ต๋๋ค.
๋ฐ์ํSQLiteOpenHelper
SQLiteDatabase ๋ SQLite์ ์ ๊ทผํ๋ ํด๋์ค๋ก, SQL ๋ช ๋ น์ด๋ฅผ ์คํํ๊ณ DB๋ฅผ ๊ด๋ฆฌํ๋ ๋ฉ์๋๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค. ์์ ์์์์๋ SQLiteDatabase๋ฅผ openOrCreate ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ง๋ง, ์ฌ์ค์ ์ง์ ์ฌ์ฉํ๊ณ ์ ๊ทผํ๋ ์ผ์ ํํ์ง ์์ต๋๋ค. ๋ํ ๊ธฐ์กด์ ์์ฑํ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํ ์ด๋ธ์ ์์ฑํ๊ฑฐ๋, ๋ฐ์ดํฐ ์ถ๊ฐ๋ก ์ธํ ์คํค๋ง ๋ณ๊ฒฝํ๋ ์ผ์ด ์์ ๊ฒฝ์ฐ๋ ์์ต๋๋ค. ์ด๋ฅผ ๊ฐํธํ๊ฒ ๋์์ฃผ๋ ํด๋์ค๊ฐ SQLiteOpenHelper๋ก DB ์์ฑ๊ณผ DB์ ๋ฒ์ ๊ด๋ฆฌ๋ฅผ ์์์ ํด์ฃผ๋ ํฌํผ(Helper)ํด๋์ค์ ๋๋ค.
์์ฑ์๋ ๋ค์๊ณผ ๊ฐ์ผ๋ฉฐ, ์ธ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ์๋ Cursor ๊ตฌํ์ฒด๋ฅผ ์์ฑํ๋ Factory๋ฅผ ์ ๋ฌํ ์ ์์ต๋๋ค. ๊ธฐ๋ณธ ๊ตฌํ์ฒด์ธ SQLiteCursor๋ฅผ ์ฌ์ฉํ๋ค๋ฉด null ๊ฐ์ ์ ๋ฌํ๋ฉด ๋๊ณ , ์๋ก์ด Factory๋ฅผ ์์ฑํด์ ์ ๋ฌํ ์๋ ์์ง๋ง ๋๋ถ๋ถ ์ฌ์ฉํ์ง๋ ์์ต๋๋ค.
๋ค ๋ฒ์งธ ํ๋ผ๋ฏธํฐ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฒ์ ์ ์ ๋ ฅํ๊ฒ ๋๊ณ , ๋ฒ์ ์ ์ ๋ฐ์ดํธํ ๊ฒฝ์ฐ์ ํด๋น ํ๋ผ๋ฏธํฐ์๋ ๊ธฐ์กด์ ๋ฒ์ ๋ณด๋ค ๋๊ฒ ๋์ ์ ํ์ฌ ๋ฒ์ ๋ณ๊ฒฝ์ ํ ์ ์์ต๋๋ค.
class TodoSQLHelper private constructor(context: Context ) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { override fun onCreate(db: SQLiteDatabase?) { db?.execSQL(CREATE_QUERY) } override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) { // TODO("Not yet implemented") } companion object { const val DATABASE_VERSION = 1 const val DATABASE_NAME = "Todo.db" var instance : TodoSQLHelper? = null fun getInstance(context: Context) : TodoSQLHelper{ return if(instance == null) TodoSQLHelper(context.applicationContext) else instance!! } } }
DB์ ์์ฑ ์์ ์ SQLiteOpenHelper ์ธ์คํด์ค๋ฅผ ์์ฑ๋ ๋๋ก ์ฐฉ๊ฐํ ์ ์์ง๋ง ์ค์ ๋ก๋ ์ธ์คํด์ค๋ฅผ ์์ฑํ ํ getReadableDatabase, getWritalbeDatabase ๋ฉ์๋๋ฅผ ํธ์ถํ ๋ DB๊ฐ ์์ฑ๋ฉ๋๋ค. SQLiteOpenHelper ์ธ์คํด์ค ์ด๋ฏธ ์์ฑ์ ํ๋ค๋ฉด ๊ทธ๊ฒ์ ์ฌ์ฉํ๊ณ ํ ๋ฒ๋ ์์ฑํ ์ ์ด ์๋ค๋ฉด ์๋ก ์์ฑ ํ onCreate ๋๋ onUpgrade ์ฝ๋ฐฑ ๋ฉ์๋๊ฐ ํธ์ถ๋ฉ๋๋ค.
SQLiteOpenHelper ๋ ์ถ์ํด๋์ค์ด๋ฉฐ ํ ํ๋ฆฟ ๋ฉ์๋ ํจํด์ ์ฌ์ฉํ์ฌ ๋ง๋ค์ด ๋์ ๊ฒ์ผ๋ก, ์ ์ฝ๋์์ ํด๋์ค๋ฅผ ์์ํด์ ๋ง๋ ํด๋์ค๊ฐ TodoSQLHelper ์ ๋๋ค. onCreate, onUpgrade ๋ฑ ์ฝ๋ฐฑ๋ฉ์๋๋ฅผ ์ฌ์ ์ํ์ฌ ์ํ๋ SQL๋ฌธ์ ์คํํ ์ ์๋๋ก ๊ตฌํํ ์ ์์ต๋๋ค. ์ ์์์์๋ ํฌํผํด๋์ค๋ก DB๊ฐ ์์ฑ ์ TODO ํ ์ด๋ธ์ด ์์ฑ๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... Thread { db = TodoSQLHelper.getInstance(this).writableDatabase }.start() ... }
๋ํ DB ํฌํผ๋ ์ฑ ์ ์ฒด์์ ๊ณตํต์ผ๋ก ํ ๊ฐ์ ์ธ์คํด์ค๋ง ์ฌ์ฉํ ์ ์๋๋ก ์ฑ๊ธํค์ผ๋ก ๊ตฌํํด์ผ ํฉ๋๋ค. getInstance ๋ฉ์๋์์ Context๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์ ์ค์ ํด๋์ค ์์ฑ์ applicationContext๋ฅผ ์ฌ์ฉํ์ฌ ์ฑ ์ ์ฒด์์ ๋จ์ผ ์ธ์คํด์ค๋ง ์์ฑํ ์ ์๋๋ก ๋ฐํํ๊ณ ์์ต๋๋ค. ์ดํ ์ค์ DB ๋ SQLiteOpenHelper ํด๋์ค์ getWritableDatabase, getReableDatabase ๋ฉ์๋์ ๋ฐํ ๊ฐ์ผ๋ก ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค. ๋ง ๊ทธ๋๋ก Read-Only DB๋ฅผ ๋ฐํํ๋์ง์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๊ฒ ๋๋๋ฐ ์์ธํ ๋ด์ฉ์ ๊ณต์๋ฌธ์๋ฅผ ํ์ธํ์๋ฉด ๋ฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ DB๋ฅผ ์์ฑํ๊ฑฐ๋ ์ ๊ทธ๋ ์ด๋ ์์ ์ ์ค๋ ๊ฑธ๋ฆด ์ ์๋ ์์ ์ด๊ธฐ ๋๋ฌธ์ ๋ฉ์ธ ์ค๋ ๋๊ฐ ์๋ ๋ฐฑ๊ทธ๋ผ์ด๋ ์ค๋ ๋์์ ์คํํด์ผ ํฉ๋๋ค.
๋ฐ์ดํฐ ์ถ๊ฐํ๊ธฐ
private fun bindViews() = with(binding) { ... Thread { val values = ContentValues().apply { put(COLUMN_TITLE, title) put(COLUMN_DESCRIPTION, description) put(COLUMN_DATE, date) } db?.insert(TABLE_NAME, null, values) updateRecyclerView() }.start() ... }
SQLiteOpenHelper ํด๋์ค๋ฅผ ์ฌ์ฉํ๋๋ผ๋ ์์์ ๋ฐ์ดํฐ ์ถ๊ฐํ๋ ๋ฐฉ์์ผ๋ก SQL๋ฌธ์ ์์ฑํ ํ execSQL ๋ฉ์๋๋ฅผ ์ด์ฉํ๋ฉด ์ ์์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค. ํ์ง๋ง, SQLiteDatabase ํด๋์ค ๋ด์๋ ๋ฐ์ดํฐ ์ถ๊ฐ๋ฅผ ์ฝ๊ฒ ํ๊ธฐ ์ํ ๋ฉ์๋๊ฐ ์ ์๋์ด ์์ด ์๊ฐํ๋ ค๊ณ ํฉ๋๋ค. ๊ทธ ์ ์ ์ถ๊ฐํ ๋ฐ์ดํฐ๋ฅผ ContentValues์ Key์ Value์ ํํ๋ก ์ ์ฅํ ์ ์์ต๋๋ค. Key๋ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ ํ ์ด๋ธ์ Colume ๋ช ์ ์ ์ ํ ํด๋น ์ปฌ๋ผ์ ๋์ ํ ๋ฐ์ดํฐ๋ฅผ putํ๋๋ก ํ์ฌ ์์ฑํ์์ต๋๋ค. ์ดํ insert ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ฉด ์ฝ๊ฒ ๋ฐ์ดํฐ๊ฐ ์ถ๊ฐํ ์ ์์ต๋๋ค.
๋ฐ์ดํฐ ์ ๋ฐ์ดํธ
private fun updateTodoData(position: Int) { ... Thread { val values = ContentValues().apply { put(COLUMN_TITLE, title) put(COLUMN_DESCRIPTION, description) put(COLUMN_DATE, date) } val selection = "$_ID = ?" val selectionArg = arrayOf("$position") db?.update(TABLE_NAME, values, selection, selectionArg) updateRecyclerView() }.start() ... }
์ ๋ฐ์ดํธ๋ ๋ง์ฐฌ๊ฐ์ง๋ก SQL ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ์ฌ execSQL ๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด ๋์ง๋ง ContentValues๋ก ์ ๋ฐ์ดํธ ๋ฐ์ดํฐ๋ฅผ ์ ์ํ ํ update ๋ฉ์๋๋ก ๋ณ๊ฒฝํ ์ ์์ต๋๋ค. ์ฌ๊ธฐ์ selection์ WHERE ๋ฌธ์ ํด๋นํ์ฌ ๋ณ๊ฒฝํด์ผํ ์ปฌ๋ผ ๋ช ์ ์ ํํ๊ฒ ๋๊ณ selectionArg๋ ์ ํ๋ ์ปฌ๋ผ ๋ช ์ ๊ฐ์ ์ ๋ ฅํ์ฌ ํด๋น ๊ฐ์ผ๋ก ๋ณ๊ฒฝ๋ ์ ์๋๋ก ํฉ๋๋ค.
๋ฐ์ดํฐ ์กฐํํ๊ธฐ
private fun updateRecyclerView() { if(db == null) return todoAdapter.clear() Thread { val list = mutableListOf<Todo>() val projection = arrayOf(COLUMN_TITLE, COLUMN_DESCRIPTION, COLUMN_DATE) val sortOrder = "$_ID ASC" val cursor = db?.query( TABLE_NAME, projection, null, null, null, null, sortOrder ) while (cursor.moveToNext()) { list.add( Todo( cursor.getString(0), cursor.getString(1), cursor.getString(2) ) ) } todoAdapter.addAll(list) runOnUiThread { todoAdapter.notifyDataSetChanged() } }.start() }
๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ ๋ฐฉ๋ฒ๋ query ๋ผ๋ ๋ฉ์๋๋ฅผ ์ด์ฉํ๋ฉด ์ฝ๊ฒ Cursor ๊ตฌํ์ฒด๋ฅผ ๋ฐํ๋ฐ์ ์ ์์ต๋๋ค. projection์ ์กฐํํ๋ ค๊ณ ํ๋ ์ปฌ๋ผ๋ค์ ๋ฐฐ์ด๋ก ์ ์ํ๊ณ , ๊ทธ ์ธ selection, selectionArg, groupBy, having, orderBy ๋ฑ ๋ค์ํ๊ฒ ์กฐ๊ฑด์ ์ค์ ํ ์ ์์ต๋๋ค. ์ ๋ ์ ์ฒด์ ๊ฐ๋ค์ ์กฐํํ๋ ค๊ณ selection๊ณผ selectionArg ๊ฐ์ null์ ๋์ ํ์์ต๋๋ค. ์ญ์๋ ์์ธํ ๊ฑด ๊ณต์๋ฌธ์์ ์ ๋์์์ต๋๋ค.
๋ฐ์ดํฐ ์ญ์ ํ๊ธฐ
private fun deleteTodoData(id: Int) { Thread { val selection = "$_ID = ?" val selectionArg = arrayOf("$id") db?.delete(TABLE_NAME, selection, selectionArg) updateRecyclerView() }.start() }
๋ฐ์ดํฐ ์ญ์ ํ๋ SQL๋ฌธ์ฒ๋ผ WHERE ์กฐ๊ฑด์ ๋ง ์ค์ ํ delete ๋ฉ์๋๋ง ํธ์ถํ๋ฉด ์กฐ๊ฑด์ ๋ง๋ ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ ์ ์์ต๋๋ค.
๋ฐ์ํ๋ฒ์ ์ ๋ฐ์ดํธ
object TodoContract { object TodoEntry : BaseColumns { const val TABLE_NAME = "TODO" const val COLUMN_TITLE = "title" const val COLUMN_DESCRIPTION = "description" const val COLUMN_DATE = "date" const val COLUMN_TIME = "time" const val CREATE_QUERY = "CREATE TABLE IF NOT EXISTS $TABLE_NAME (" + "$_ID INTEGER PRIMARY KEY AUTOINCREMENT, " + "$COLUMN_TITLE TEXT, " + "$COLUMN_DESCRIPTION TEXT, " + "$COLUMN_DATE TEXT)" const val CREATE_QUERY2 = "CREATE TABLE IF NOT EXISTS $TABLE_NAME (" + "$_ID INTEGER PRIMARY KEY AUTOINCREMENT, " + "$COLUMN_TITLE TEXT, " + "$COLUMN_DESCRIPTION TEXT, " + "$COLUMN_DATE TEXT, " + "$COLUMN_TIME TEXT" const val DROP_QUERY = "DROP TABLE IF EXISTS $TABLE_NAME" } }
SQLiteOpenHelper ์์ฑ์์ ๋งค๊ฐ๋ณ์์ DB ๋ช ์ด ์์ด ์๋ก์ด DB๋ฅผ ์์ฑํ ๋๋ง๋ค DB Helper ํด๋์ค๊ฐ ํ์ํฉ๋๋ค. ๊ฐ๋ฅํ๋ฉด ํ๋์ ํฌํผํด๋์ค๋ฅผ ์ฌ์ฉํ๋ฉด ์ข์ง๋ง, ์ฌ๋ฌ DB๋ฅผ ์ฌ์ฉํ๋ค๊ฐ๋ DB๋ฝ ๋ฌธ์ ๊ฐ ์์์๋ ์๊ธฐ์ ํ๋์ DB์๋ ํ๋์ Helper ํด๋์ค๋ฅผ ๋ง๋๋๊ฒ ์ข์ต๋๋ค.
class TodoSQLHelper private constructor(context: Context ) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { override fun onCreate(db: SQLiteDatabase?) { db?.execSQL(CREATE_QUERY2) } override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) { Log.d("msg", "onUpgrade") when(oldVersion){ 1 -> db?.execSQL("ALTER TABLE $TABLE_NAME ADD COLUMN " + "$COLUMN_TIME TEXT") } } companion object { const val DATABASE_VERSION = 2 const val DATABASE_NAME = "Todo.db" var instance : TodoSQLHelper? = null fun getInstance(context: Context) : TodoSQLHelper{ return if(instance == null) TodoSQLHelper(context.applicationContext) else instance!! } } }
onCreate ์ฝ๋ฐฑ๋ฉ์๋๊ฐ SQLiteOpenHelper ํด๋์ค๋ฅผ ์์ฑํ ํ getWritableDatabase, getReableDatabase ๋ฅผ ํธ์ถํ ๋ ์คํ๋๋ค๊ณ ํ์์ต๋๋ค. onUpgrade ์ฝ๋ฐฑ๋ฉ์๋๋ ์ด๋ฏธ DB๊ฐ ์์ฑ๋ ์์ ์์ ์คํค๋ง๋ฅผ ๋ณ๊ฒฝํ๊ฑฐ๋, ํ ์ด๋ธ์ ์ถ๊ฐํ๋ ๋ฑ DB์ ๋ฒ์ ์ด ์ ๋ฐ์ดํธ๊ฐ ๋ ๋ ํธ์ถ์ด ๋ฉ๋๋ค.
oldVersion์ ์ด์ ๋ฒ์ ์ด๊ณ newVersion์ด ์๋ก์ด ๋ฒ์ ์ผ๋ก downgrade๋ ๋์ง ์์ต๋๋ค. ์ฆ, ๋ฒ์ ์ ๋ฐ์ดํธ ์์๋ ์ด์ ๋ฒ์ ๋ณด๋ค๋ ๋์ ์ซ์๋ฅผ ๋์ ํ์ฌ์ผ๋ง ํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ onCreate, onUpgrade ๋ ๋ฉ์๋ ์ค ๋ฌด์กฐ๊ฑด ํ๋๋ง ์คํ์ด ๋ฉ๋๋ค. ํ์ฌ DB ๋ฒ์ ์ด 4๋ก ์ต์ ๋ฒ์ ์ด ๋์์ ๋ ์๋กญ๊ฒ ์ฑ์ ์ค์นํ ์ฌ๋์๊ฒ๋ onCreate ๋ฉ์๋๋ก SQL ์ฟผ๋ฆฌ๋ฅผ ์คํํ๊ณ , ์ด์ ์ DB๋ฅผ ์ค์นํ ์ฌ๋์๊ฒ๋ onUpgrade ๋ฉ์๋๋ก ์คํค๋ง ๋๋ ํ ์ด๋ธ์ ๋ณ๊ฒฝํฉ๋๋ค.์ต์ ๋ฒ์ ์ ์ฑ์ ์ค์นํ ์ฌ๋์๊ฒ๋ onCreate ๋ฉ์๋๊ฐ ํธ์ถ๋์ด CREATE_QUERY2๋ก SQL๋ฅผ ์คํํ๊ณ , ์ด๋ฏธ ์ด์ ๋ฒ์ ์ DB๊ฐ ์ค์น๋ ์ฌ๋์๊ฒ๋ onUpgrade๋ก ALTER TABLE ์ฟผ๋ฆฌ๋ฅผ ์คํํฉ๋๋ค. ์์ ๊ทธ๋ฆผ์ onUpgrade๋ก time ์ปฌ๋ผ์ ์ถ๊ฐํ์ฌ ํ ์ด๋ธ์ ๋ณ๊ฒฝํ๋ ๊ฒ์ ์ ์ ์์ต๋๋ค.
์ฐธ๊ณ
- ์๋๋ก์ด๋ ํ๋ก๊ทธ๋๋ฐ Next Step p169 ~ p184
- ์๋๋ก์ด๋ ์ฑ ํ๋ก๊ทธ๋๋ฐ p537 ~ p551
๋ฐ์ํ'Android' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Android] ContentProvider ๊ตฌํ ๋ฐ ์ฌ์ฉ๋ฒ (0) 2022.02.05 [Android] Room ์ดํด ๋ฐ ํ์ฉ (0) 2022.01.01 [Android] Context, ContextWrapper, ContextImpl ์ดํด (0) 2021.11.21 [Android] Preference, Shared Preference? (0) 2021.11.19 [Android] Thread, Handler, Looper, Message Queue ๋ค๋ฃจ๊ธฐ (0) 2021.11.11