FrontEnd/Android

[Android] SQLite Database

Satisfaction 2021. 6. 14. 18:51

 

android app 내부에서 프라이빗하게 사용할 수 있는 간단한 SQLite에 대해 알아보자.

 

파일 형태로 입출력할 경우 검색을 위해서는 먼저 모든 file을 스캔해야 하고

검색, 수정, 삭제 등 모든 기능을 직접 구현해야 해서 매우 귀찮기 때문에,

용량이 비교적 큰 데이터를 다룰 때는 raw file과 직접 IO하는 것 보다 DB를 사용하는 편이 더 좋다.

 

DBHelper

가장 먼저 Database와 통신하기 위한 클래스를 작성한다.

class MyDBHelper(
    val context: Context,
) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
    companion object {
        val DB_NAME = "mydb.db"
        val DB_VERSION = 1
        val TABLE_NAME = "products"
        val PID = "pid"
        val PNAME = "pname"
        val PQUANTITY = "pquantity"
    }

    override fun onCreate(db: SQLiteDatabase?) {
        val create_table = "CREATE TABLE IF NOT EXISTS $TABLE_NAME(" +
                "$PID INTEGER PRIMARY KEY AUTOINCREMENT," +
                "$PNAME text," +
                "$PQUANTITY INTEGER);"
        db!!.execSQL(create_table)
    }
    override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
        val drop_table = "DROP TABLE IF EXISTS $TABLE_NAME"
        db!!.execSQL(drop_table)
        onCreate(db)
    }
}

DB_NAME은 데이터베이스의 이름

DB_VERSION은 데이터베이스의 버전 (앱 버전 업데이트 시 데이터베이스의 구조가 변경되면 이 값을 바꾸어 테이블을 다시 생성할 수 있다)

TABLE_NAME 데이터베이스의 테이블 이름

PID 데이터베이스의 TABLE_NAME에 해당하는 테이블의 attribute 이름

PNAME 데이터베이스의 TABLE_NAME에 해당하는 테이블의 attribute 이름

PQUANTITY 데이터베이스의 TABLE_NAME에 해당하는 테이블의 attribute 이름

 

onCreate는 데이터베이스가 처음 생성될 때 호출된다.

 

onUpgrade는 DB_VERSION값이 현재 DB 버전과 다를 때 호출되며,

함수 내부에서 DB를 삭제하고 onCreate를 호출함으로써 다시 데이터베이스를 생성한다.

 

DBHelper에서 다룰 데이터 정보는 다음과 같다.

data class Product(var pId:Int, var pName: String, var pQuantity:Int)

위 3개 멤버 변수를 db에 저장하고, CRUD해 볼 것이다.

 

Read

class MyDBHelper(
    val context: Context,
) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
    companion object {
        val DB_NAME = "mydb.db"
        val DB_VERSION = 1
        val TABLE_NAME = "products"
        val PID = "pid"
        val PNAME = "pname"
        val PQUANTITY = "pquantity"
    }
    //...
    fun getAllProducts(): ArrayList<Product> {
        val list: ArrayList<Product> = ArrayList<Product>()

        val strSql = "SELECT * FROM $TABLE_NAME"
        val db = readableDatabase
        //cursor hold result of sql execution
        val cursor = db.rawQuery(strSql, null)
        //column count
        val attrCnt = cursor.columnCount

        //데이터 갯수(row count)가 0일 경우 종료
        if (cursor.count == 0)
            return list
        else{
          cursor.moveToNext()
          do {
                var pid = 0
                var pName = ""
                var pQuantity = 0
                for (i in 0 until attrCnt) {
                    when (i) {
                        0 -> pid = cursor.getString(i).toInt()
                        1 -> pName = cursor.getString(i)
                        2 -> pQuantity = cursor.getString(i).toInt()
                    }
                }
                //build Product and add it to list
                list.add(Product(pid, pName, pQuantity))
            } while (cursor.moveToNext())
        }
        //free resources
        cursor.close()
        db.close()
        
        return list
    }
    //...
}

데이터베이스에 저장된 모든 Product 정보를 뽑아오는 코드이다.

 

보다시피 간단한 SQL을 사용하고 있는데, 아예 문외한이라면 응용하기는 힘들테니

간단하게 SQL을 공부하고 오는 것을 추천한다.

 

https://satisfactoryplace.tistory.com/215?category=907961 

 

Create

    fun insertProduct(product: Product): Boolean {
        val values = ContentValues()
        values.put(PNAME, product.pName)
        values.put(PQUANTITY, product.pQuantity)
        val db = writableDatabase
        return if (db.insert(TABLE_NAME, null, values) > 0) {
            db.close()
            true
        } else {
            db.close()
            false
        }
    }

ContentValues에 값을 넣고, db.insert할 때 그 인스턴스를 입력하는 식으로 구현한다.

 

Update

    fun updateProduct(product: Product): Boolean {
        val pid = product.pId

        val sql = "SELECT * FROM $TABLE_NAME WHERE $PID='$pid'"
        val db = readableDatabase
        val cursor = db.rawQuery(sql, null)

        val flag = cursor.count != 0
        if (flag) {
            cursor.moveToFirst()
            val values = ContentValues()
            values.put(PNAME, product.pName)
            values.put(PQUANTITY, product.pQuantity)
            db.update(TABLE_NAME, values, "$PID=?", arrayOf(pid.toString()))
        }
        cursor.close()
        db.close()

        return flag
    }

Update는 SQL로 UPDATE TABLE SET [values...] WHERE [condition...] 하지 않고

SELECT로 먼저 cursor를 얻은 뒤 db.update해서 수정한다.

 

Delete

    fun deleteProduct(pid: String): Boolean {
        val sql = "SELECT * FROM $TABLE_NAME WHERE $PID='$pid'"
        val db = readableDatabase
        val cursor = db.rawQuery(sql, null)

        val flag = cursor.count != 0
        if (flag) {
            cursor.moveToFirst()
            db.delete(TABLE_NAME, "$PID=?", arrayOf(pid))
        }
        cursor.close()
        db.close()

        return flag
    }

Delete도 Update와 유사한 방식으로 사용한다.

 

Use in activity

class ProductActivity : AppCompatActivity() {
    lateinit var binding:ActivityProductBinding
    lateinit var myDBHelper: MyDBHelper

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding= ActivityProductBinding.inflate(layoutInflater)
        setContentView(binding.root)

        myDBHelper= MyDBHelper(this)
    }
    //...
}

단순히 MyDBHelper의 생성자에서 Context를 전달하면 된다.

onCreate 이후에는 myDBHelper.getAllProducts() 처럼 사용해 자유자재로 사용할 수 있게 되었다.