본문 바로가기

안드로이드 앱 스쿨 2기/Android

[11주차 - 수] SQLIteDataBase

SQLite 데이터 베이스

  • 안드로이드에서 사용하는 내장 데이터 베이스로 표준 SQL문을 사용하는 관계형 데이터 베이스이다.
  • MySQL 과 유사한 문법을 사용하고 있으며 일반적인 관계형 데이터 베이스가 가지고 있는 기능을 가지고 있다.

동작 방식

  • SQLite 데이터베이스는 임베디드형 데이터베이스로써 데이터베이스를 사용하는 애플리케이션에 셋팅되는 데이터 베이스이다.
  • 안드로이드는 안드로이드 OS에 내장되어 있으며 개발자가 만드는 애플리케이션은 안드로이드 OS에게 쿼리문을 전달하고 안드로이드 OS가 직접 데이터 베이스에 대한 처리를 하게된다.

작성 방식

  • 안드로이드에서의 SQLite 데이터베이스 사용은 쿼리문을 이용하는 방법과 제공되는 클래스를 이용하는 방법 두 가지가 있다.
  • 쿼리문을 이용하는 방식은 일반적인 SQL문을 사용하며 MySQL과 유사한 문법을 사용한다.
  • 클래스를 이용하는 방법은 개발자가 정해줘야 하는 몇 가지 정보를 제공하면 쿼리문이 생성되고 실행되는 구조이다.

SQLite OpenHelper

  • 안드로이드에서 SQLite 데이터 베이스를 사용하려면 SQLiteOpenHelper를 상속받은 클래스를 만들어야 한다.
  • 이 클래스는 사용할 데이터베이스의 이름을 설정하는 것 뿐만 아니라 다음 기능들을 제공하고 있다.
  • 지정된 데이터베이스 파일을 사용하려고 할 때 파일이 없으면 파일을 만들고 onCreate 메서드를 호출한다. 이 메서드에서는 테이블을 만드는 쿼리를 실행해주면 된다.
  • 애플리케이션을 서비스하다가 데이터 베이스 구조를 변경하려면 데이터베이스의 버전을 변경하면 된다. 버전을 변경하면 onUpgrade 메서드가 호출되고 여기에서 테이블을 새로운 구조로 변경해주는 작업을 해주면 된다.

파일 구조

 

1. DBHelper.kt 

 

// 생성자로 Context 받아야한다

// Test.db :사용할 데이터 베이스 파일의 이름
// null : NullFactory, Null 에 대한 처리를 어떻게 할 것인가.. Null 셋팅하면 된다
// 1 : 버전

// 예를 들어 학생들의 시험 점수를 입력받는 테이블이 있는데
// 국어, 수학, 영어 점수를 받는다 ( 과목의 점수들을 열에 해당)
// 그런데, 어는 한 학생 A 가 영어 시험을 안받으면 영어 점수가 없는데, 이러면 안된다
// 그래서 점수가 없으므로 null 값이 들어 가도록 해야한다
// 즉, 행에대한 값은 비어있으면 안된다

class DBHelper(context:Context) : SQLiteOpenHelper(context, "Test.db", null, 1) {
    // 데이터 베이스 파일이 없으면 만들고 이 메서드를 호출해준다.
    // 테이블을 생성하는 작업을 수행하면 된다.
    override fun onCreate(sqliteDatabase: SQLiteDatabase?) {
        // 테이블의 구조를 정의한다.
        // 항상 최신의 형태를 가지도록 한다

        // create table 테이블이름
        // (컬럼이름 자료형 제약조건 ... )
        // 자료형 : 정수 - integer, 문자열 - text, 실수 - real, 날짜 - date
        // 제약조건 : 저장할 수 있는 값에 대한 조건
        // primary key : null을 허용하지 않고 중복된 값을 허용하지 않는다.
        //               각 행들을 개별적으로 구분할 수 있는 값을 저장하기 위해 사용한다.
        // autoincrement : 컬럼에 저장할 값을 지정하지 않으면 1부터 1씩 증가되는 값이 자동으로 저장한다.
        // not null : null 값을 허용하지 않는다. 즉, 개발자가 무조건 값을 정해줘야 한다.

        val sql = """create table TestTable
            (idx integer primary key autoincrement,
            textData text not null,
            intData integer not null,
            doubleData real not null,
            dateData date not null)
        """.trimIndent()

        // 쿼리문을 수행한다.
        sqliteDatabase?.execSQL(sql)
    }

    // 사용하는 데이터 베이스 파일의 버전이 변경되어 있을 때 호출되는 메서드
    // 부모의 생성자의 마지막에 넣어준 버전 번호가 데이터 베이스 파일에 기록 버전 보다 높을 때 호출된다.
    // 과거에 만들어진 테이블을 현재의 구조가 될 수 있도록 테이블을 수정하는 작업을 하면 된다.
    // ex) 과거에는 컬럼이 3개이나 현재는 업데이트로 인해 컬럼이 5개 이므로 업데이트가 필요하다
    // 즉, 과거의 데이터베이스를 쓰고 있는 사람들의 위함이다
    override fun onUpgrade(sqliteDatabase: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
        TODO("Not yet implemented")
    }
    
}

 

[챗 GPT 설명]

 

DBHelper 클래스는 SQLiteOpenHelper를 상속하여 SQLite 데이터베이스를 생성하고 관리하는 역할을 한다.

onCreate() 메서드는 데이터베이스 파일이 생성되어 있지 않을 때 호출되어 테이블을 생성하고, onUpgrade() 메서드는 데이터베이스 파일의 버전이 변경되었을 때 호출되어 업그레이드 작업을 수행할 수 있다.

 

sqliteDatabase?.execSQL(sql)
  • sqliteDatabase 객체의 execSQL() 메서드를 사용하여 SQL 문을 실행합니다.
  • 이를 통해 테이블이 생성됩니다.

 

2. activity_main.xml

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="insert" />

    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="select all" />

    <Button
        android:id="@+id/button3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="select condition" />

    <Button
        android:id="@+id/button4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="update" />

    <Button
        android:id="@+id/button5"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="delete" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="TextView"
        android:textAppearance="@style/TextAppearance.AppCompat.Large" />
</LinearLayout>

 

3. MainActivity

 

class MainActivity : AppCompatActivity() {
    lateinit var activityMainBinding: ActivityMainBinding

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

        activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(activityMainBinding.root)

        activityMainBinding.run{
            button.setOnClickListener {
                // 현재 시간을 구해 년-월-일 양식의 문자열을 만들어준다.
                val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
                val now = sdf.format(Date())

                // 저장할 정보를 가지고 있는 객체를 생성한다.
                // - idx 는 autoincrement 으로 자동으로 증가되므로, 그냥 0 값 넣어주었다
                val obj1 = TestClass(0, "문자열1", 100, 11.11, now)
                DAO.insertData(this@MainActivity, obj1)

                val obj2 = TestClass(0, "문자열2", 200, 22.22, now)
                DAO.insertData(this@MainActivity, obj2)

            }

            button2.setOnClickListener {
                // 데이터를 가져온다.
                val dataList = DAO.selectAllData(this@MainActivity)

                // 출력
                textView.text = ""

                for(obj in dataList){
                    textView.append("idx : ${obj.idx}\n")
                    textView.append("textData : ${obj.textData}\n")
                    textView.append("intData : ${obj.intData}\n")
                    textView.append("doubleData : ${obj.doubleData}\n")
                    textView.append("dateData : ${obj.dateData}\n\n")
                }
            }

            button3.setOnClickListener {
                val obj = DAO.selectData(this@MainActivity, 1)

                textView.text = "idx : ${obj.idx}\n"
                textView.append("textData : ${obj.textData}\n")
                textView.append("intData : ${obj.intData}\n")
                textView.append("doubleData : ${obj.doubleData}\n")
                textView.append("dateData : ${obj.dateData}\n")
            }

            button4.setOnClickListener {
                val obj = DAO.selectData(this@MainActivity, 1)

                obj.textData = "새로운 문자열"

                DAO.updateData(this@MainActivity, obj)
            }

            button5.setOnClickListener {
                DAO.deleteData(this@MainActivity, 1)
            }

        }

    }
}

// DBHelper 의 TestTable 테이블안에 있는 컬럼들 과 같은 타입을 맞춰준다
data class TestClass(var idx:Int,
                     var textData:String,
                     var intData :Int,
                     var doubleData:Double,
                     var dateData:String)

 

4. DAO.kt

- DAO : Data Access Object

 

class DAO {

    companion object {

        // Create : 저장
        fun insertData(context: Context, data: TestClass) {
            // autoincrement가 있는 컬럼은 제외하고 나머지만 지정한다.
            // 처음에는 값이 들어갈 부분은 ? 으로 하면된다
            val sql = """insert into TestTable
                | (textData, intData, doubleData, dateData)
                | values (?, ?, ?, ?)
            """.trimMargin()

            // ? 에 설정할 값을 배열에 담아준다. (하나만 있더라도 무조건 배열로 담아준다)
            // values (?, ?, ?, ?) 안에 순서 대로 넣어진다 (1대1 대응)
            val arg1 = arrayOf(
                data.textData, data.intData, data.doubleData, data.dateData
            )
            // 데이터베이스 오픈
            val sqliteDatabase = DBHelper(context)
            // 쿼리 실행 (쿼리문, ?에 셋팅할 값 배열)
            sqliteDatabase.writableDatabase.execSQL(sql, arg1)
            // 데이터 베이스를 닫아준다.
            sqliteDatabase.close()
        }

        // Read Condition : 조건에 맞는 행 하나를 가져온다.
        // - 먼저 autoincrement 을 가지고 있는 컬럼 (여기서는 idx)부터 조건을 만들어준다
        fun selectData(context: Context, idx:Int) : TestClass{
            // 쿼리문
            val sql = "select * from TestTable where idx=?"
            // select 는 무조건 무자열 배열로 담아둔다
            // ?에 들어갈 값 (문자열 배열)
            val arg1 = arrayOf("$idx")

            // 데이터베이스 오픈
            val dbHelper = DBHelper(context)
            // 쿼리실행
            val cursor = dbHelper.writableDatabase.rawQuery(sql, arg1)
            cursor.moveToNext()

            // 가져올 테이블(TestTable) 안에 있는 컬럼의 이름을 지정하여 컬럼의 순서값을 가져온다.
            val idx1 = cursor.getColumnIndex("idx")
            val idx2 = cursor.getColumnIndex("textData")
            val idx3 = cursor.getColumnIndex("intData")
            val idx4 = cursor.getColumnIndex("doubleData")
            val idx5 = cursor.getColumnIndex("dateData")

            // 데이터를 가져온다.
            val idx = cursor.getInt(idx1)
            val textData = cursor.getString(idx2)
            val intData = cursor.getInt(idx3)
            val doubleData = cursor.getDouble(idx4)
            val dateData = cursor.getString(idx5)

            val testClass = TestClass(idx, textData, intData, doubleData, dateData)

            dbHelper.close()
            return testClass
        }

        // Read All : 모든 행을 가져온다
        fun selectAllData(context: Context):MutableList<TestClass>{
            // 모든 행을 가져오는 쿼리문을 작성한다.
            val sql = "select * from TestTable"

            // 데이터베이스 오픈
            val dbHelper = DBHelper(context)
            // 쿼리 실행
            // - 두 번째 매개변수(selectionArgs)에는 ? 들어갈 값을 넣어주면 되는데, ? 없으므로 null 값을 넣어준다
            val cursor = dbHelper.writableDatabase.rawQuery(sql, null)
            // cursor 객체는 쿼리문에 맞는 행에 접근할 수 있는 객체가 된다.
            // 처음에는 아무 행도 가르치고 있지 않는다.
            // moveToNext 메서드를 호출하면 다음 행에 접근할 수 있다.
            // 이때 접근할 행이 있으면 true를 반환하고 없으면  false를 반환한다.
            // - while 문 으로 쓴다

            val dataList = mutableListOf<TestClass>()

            while(cursor.moveToNext()){
                // 가져올 테이블(TestTable) 안에 있는 컬럼의 이름을 지정하여 컬럼의 순서값을 가져온다.
                val idx1 = cursor.getColumnIndex("idx")
                val idx2 = cursor.getColumnIndex("textData")
                val idx3 = cursor.getColumnIndex("intData")
                val idx4 = cursor.getColumnIndex("doubleData")
                val idx5 = cursor.getColumnIndex("dateData")

                // 데이터를 가져온다.
                val idx = cursor.getInt(idx1)
                val textData = cursor.getString(idx2)
                val intData = cursor.getInt(idx3)
                val doubleData = cursor.getDouble(idx4)
                val dateData = cursor.getString(idx5)

                val testClass = TestClass(idx, textData, intData, doubleData, dateData)
                dataList.add(testClass)
            }

            dbHelper.close()

            return dataList
        }

        // Update : 조건에 맞는 행의 컬럼의 값을 수정한다.
        fun updateData(context:Context, obj:TestClass){
            // 쿼리문 의미
            // - idx가 ? 인 행의 textData, intData, doubleData, dateData 컬럼의 값을 변경한다
            val sql = """update TestTable
                | set textData=?, intData=?, doubleData=?, dateData=?
                | where idx=?
            """.trimMargin()

            // ? 에 들어갈 값
            val args = arrayOf(obj.textData, obj.intData, obj.doubleData, obj.dateData, obj.idx)
            // 쿼리 실행
            val dbHelper = DBHelper(context)
            dbHelper.writableDatabase.execSQL(sql, args)
            dbHelper.close()
        }

        // Delete : 조건 맞는 행을 삭제한다.
        fun deleteData(context:Context, idx:Int){
            // 쿼리문
            // TestTable에서 idx가 ? 인 행을 삭제한다.
            val sql = "delete from TestTable where idx = ?"
            // ?에 들어갈 값
            val args = arrayOf(idx)
            // 쿼리 실행
            val dbHelper = DBHelper(context)
            dbHelper.writableDatabase.execSQL(sql, args)
            dbHelper.close()
        }
    }

}

[챗 GPT 설명]

 

1. inserDate 부분

val dbHelper = DBHelper(context)
  • -DBHelper 클래스는 SQLiteOpenHelper를 상속받아 데이터베이스를 생성하고 관리합니다.
dbHelper.writableDatabase.execSQL(sql, arg1)
  • writableDatabase 속성을 사용하여 데이터베이스를 열고 SQL 문을 실행합니다.
  • execSQL() 메서드는 SQL 문을 실행하여 데이터베이스에 쿼리를 실행합니다.

2. selectData 은 selectAllData 과 거의 비슷하다

 

3. selectAllData 부분

val cursor = dbHelper.writableDatabase.rawQuery(sql, null)
  • dbHelper 객체의 writableDatabase 속성을 사용하여 데이터베이스를 열고 SQL 문을 실행합니다.
  • rawQuery() 메서드는 SQL 문을 실행하여 결과로 Cursor 객체를 반환합니다.
  • sql 변수에 저장된 SELECT 문을 실행하여 데이터베이스로부터 결과를 가져옵니다.

 

[결과]

 

 

 

출처 : 안드로이드 앱스쿨 2기 윤재성 강사님

 

 

 

 

 

'안드로이드 앱 스쿨 2기 > Android' 카테고리의 다른 글

[13주차 - 화] JSON 문서  (0) 2023.07.18
[12주차 - 금] 9 patch 이미지  (0) 2023.07.14
[11주차 - 수] Assets  (0) 2023.07.05
[11주차 - 수] Raw  (0) 2023.07.05
[11주차 - 화] FileStream  (0) 2023.07.04