[Android] Database 프로그램 정리

Minjun Kim·2023년 9월 8일
0

Android

목록 보기
35/47
post-thumbnail

📝 SeSAC의 'JetPack과 Kotlin을 활용한 Android App 개발' 강좌를 정리한 글 입니다.


📇 Database 개요

데이터 영속화에 있어서 "그냥 Read/Write 와 SharedPreference 로 충분한 것 아니냐?" 라고 생각 할 수도 있다. Read/Write 는 주로 이미지를, SharedPreference 는 주로 설정 파일(Map) 에 사용된다.
얼핏 보면 SharedPreference 로 커버칠 수 있게 보이지만, K-V 로 데이터를 저장하기 때문에 데이터를 구조화하기 어렵다. 그래서 대량의 데이터 저장에는 부적합히다.

대부분의 작업은 서버사이드에서 진행되지만 기기 로컬에 데이터를 저장해야 될 경우가 있다. 특히 데이터 캐싱 차원에서 많이 그렇다. 이 때 안드로이드 폰에 내장되어 있는 SQLite 를 사용할 수 있다.

데이터베이스로 데이터 영속화
SQLite(www.sqlite.org) 은 오픈소스로 만들어진 데이터 베이스로 관계형 데이터 베이스
복잡하고 구조화된 어플리케이션 데이터 저장 및 관리

  • SQLite Database 는 별도의 프로세스가 아닌 라이브러리를 이용

  • 데이터베이스는 생성한 어플리케이션의 일부로 통합

  • SQLite 를 이용한 데이터는 파일에 저장되며 /data/data/<package_name>/databases 폴더에 저장
    (내장 메모리 공간에 저장되므로 외부 앱에서 이용 불가!)

SQLite 데이터는 파일 로 저장된다.


📚 SQLiteOpenHelper

안드로이드 데이터베이스 프로그램의 핵심 클래스는 2개 이다.

  • SQLiteDatabase 클래스

  • SQLiteOpenHelper 클래스

데이터베이스에서 SQLiteDatabase 객체 이용은 필수이다. 데이터베이스를 사용한다는 것은 곧 Sql문을 사용한다는 뜻이고, 이 Sql문을 제공해주는 것이 바로 SQLiteDatabase 이기 때문이다.

SQLiteOpenHelper 는 필수는 아니지만 사용하면 프로그램의 구조가 좋아진다.
SQLiteOpenHelper 클래스는 앱을 위한 데이터베이스 관리적인 코드를 한 곳에 추상화 시킬 목적을 가진다. 여기서 관리적인 코드란 데이터베이스에 테이블을 create 하거나 alter 혹은 drop 을 위한 코드를 말한다.

📌 생성자

class DBHelper(context: Context): SQLiteOpenHelper(context, "testdb", null, 1) {
	//..............
}
  • SQLiteOpenHelper 클래스를 상속 받아 작성

  • 두 번째 매개변수는 파일명 이다.

❗ 중요

마지막 매개변수는 개발자가 제공하는 데이터베이스 버전 정보다.
이 버전 정보가 바뀌는 순간 내부적으로 감지가 이루어진다.

📌 오버라이드 함수

onCreate() : 앱이 인스톨 된 후 최초로 SQLiteOpenHelper 클래스가 이용되는 순간 한 번 호출

onUpgrade() : SQLiteOpenHelper 의 생성자에 지정한 DB 버전 정보가 변경될 때마다 호출
  • 위 2개의 함수는 자동 호출된다.

  • onCreate() 는 앱이 실행되고 최초로 SQLiteOpenHelper 클래스가 이용되는 순간 호출된다.
    즉, 최초에 자동 콜 후에 다시는 호출 되지 않는다. 때문에 거의 대부분 테이블 create 내용을 담는다.

  • onUpgrade() 에는 대부분 스키마 변경을 담는다.

📌 SQLiteDatase 객체 획득

val db: SQLiteDatabase = DBHelper(this).writableDatabase
  • SQLiteDatase 객체를 SQLiteOpenHelper 클래스로 획득

  • SQLiteOpenHelper 의 readableDatabase 혹은 writableDatabase 프로퍼티로 SQLiteDatabase 객체를 획득

  • readableDatabase 은 select문, writableDatabase 은 나머지 Sql문이라 보면 된다.

🧩 실습 예제

  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
	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"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="select"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

  • DBHelper.xml
package com.kotdev99.android.c59

class DBHelper(context: Context) : SQLiteOpenHelper(context, "testdb", null, 1) {
	override fun onCreate(p0: SQLiteDatabase?) {
		val studentSql = """
			create table tb_member (
			_id integer primary key autoincrement,
			name not null,
			email,
			phone)
		""".trimIndent()
		p0?.execSQL(studentSql)
		p0?.execSQL("insert into tb_member (name, email, phone) values " +
				"('kotdev', 'kotdev99@gmail.com', '1111')")
	}

	override fun onUpgrade(p0: SQLiteDatabase?, p1: Int, p2: Int) {
		p0?.execSQL("drop table tb_student")
		onCreate(p0)
	}
}

  • MainActivity.kt
package com.kotdev99.android.c59

class MainActivity : AppCompatActivity() {
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_main)

		val button = findViewById<Button>(R.id.button)
		button.setOnClickListener {
			// select문 으로 저장 되어 있는 데이터 뽑기
			val db: SQLiteDatabase = DBHelper(this).readableDatabase
			val cursor = db.rawQuery("select name from tb_member", null)
			if (cursor.moveToFirst()) {
				Toast.makeText(this, "${cursor.getString(0)}", Toast.LENGTH_SHORT).show()
			}
			db.close()
		}
	}
}

📲 결과


📚 execSql, rawQuery

val db = openOrCreateDatabase("testdb", Context.MODE_PRIVATE, null)
  • SQLite 를 이용하기 위한 최소한의 API

  • openOrCreateDatabase() 함수를 이용해 획득

  • SQLiteOpenHelper 객체를 이용해 획득

Sql문을 사용하기 위해서는 반드시 SQLiteDatabase 객체를 얻어야 한다. 이 객체를 얻는 방법이 2개가 있는데 하나는 SQLiteOpenHelper, 다른 하나는 openOrCreateDatabase() 라는 함수다.

즉, openOrCreateDatabase() 함수를 사용했다는 것은 OpenHelper를 사용하지 않았다는 말이다. OpenHelper를 사용했다면 OpenHelper로 객체를 얻어주면 된다.

📝 Sql 문을 실행시키는 함수

📌 execSQL

  • public void execSQL (String sql, Object[] bindArgs)
db.execSQL("create table USER_TB (" +
	"id integer primary key autoincrement," +
    "name not null," +
    "phone)")

첫 번째 매기변수에 Select문을 제외한 나머지 Sql 문을 준다.

Select문과 나머지 Sql문이 리턴 타입이 달라서 함수가 구분되어 있다.

db.execSQL("insert into USER_TB (name, phone) values (?, ?)", arrayOf<String>("kotdev", "0101111"))

? 를 사용 할 수도 있다.

두번 째 매개변수에 ? 개수만큼 배열 정보를 줘서 데이터를 ? 에 대입시킨다.

📌 rawQuery

  • public Cursor rawQuery (String sql, String[] selectionArgs)
val cursor = db.rawQuery("select * from USER_TB", null)

첫 번째 매개변수에 Select 문을 주면 된다.

만약 ? 가 있다면 두번 째 매개변수에 물음표에 해당되는 값을 주면 된다.

❗ Cursor

public abstract boolean moveToFirst()

public abstract boolean moveToLast()

public abstract boolean moveToNext()

public abstract boolean moveToPosition (int position)

public abstract boolean moveToPrevious()
  • rawQuery() 함수의 리턴 값은 Cursor 객체이며 select 된 row의 집합객체

  • Cursor 객체를 움직여 row를 선택하고 선택된 row의 column data 를 획득

즉, Cursor 로 row를 먼저 선택 후 column에 있는 데이터를 획득한다.

Cursor 가 raw를 선택하면 true, 선택하지 못하면 false를 리턴한다.

while (cursor.moveToNext()) {
	val name = cursor.getString(0)
    val phone = cursor.getString(1)
}
  • getter 함수의 매개변수가 select 된 column 의 index 값.

🧩 실습 예제

MainActivity 의 EditText 에 입력한 값을 SQLite 저장하고,
ReadActivity 에서 SQLite 테이블의 값을 출력 해보자!

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


    <EditText
        android:id="@+id/add_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="제목을 입력하세요"
        android:inputType="text" />


    <EditText
        android:id="@+id/add_content"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:hint="메모를 입력하세요"
        android:inputType="textMultiLine"
        android:scrollbars="vertical" />


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


</LinearLayout>


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

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="DB Select 결과" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Title : " />

        <TextView
            android:id="@+id/read_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Content : " />

        <TextView
            android:id="@+id/read_content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>

</LinearLayout>


  • DBHelper.xml
package com.kotdev99.android.c60

class DBHelper(context: Context) : SQLiteOpenHelper(context, "memodb", null, 1) {
	override fun onCreate(p0: SQLiteDatabase?) {
		val memoSQL = "create table tb_memo (" +
				"_id integer primary key autoincrement," +
				"title," +
				"content)"
		p0?.execSQL(memoSQL)
	}

	override fun onUpgrade(p0: SQLiteDatabase?, p1: Int, p2: Int) {
		TODO("Not yet implemented")
	}
}

  • MainActivity.kt
package com.kotdev99.android.c60

class MainActivity : AppCompatActivity() {
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_main)

		val titleView = findViewById<EditText>(R.id.add_title)
		val contentView = findViewById<EditText>(R.id.add_content)
		val addBtn = findViewById<Button>(R.id.add_btn)

		addBtn.setOnClickListener {
			val title = titleView.text.toString()
			val content = contentView.text.toString()

			val helper = DBHelper(this)
			val db = helper.writableDatabase
			db.execSQL(
				"insert into tb_memo (title, content) values (?,?)",
				arrayOf(title, content)
			)
			db.close()

			val intent = Intent(this, ReadActivity::class.java)
			startActivity(intent)
		}
	}
}

  • ReadActivity.kt
package com.kotdev99.android.c60

class ReadActivity : AppCompatActivity() {
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_read)

		val titleView = findViewById<TextView>(R.id.read_title)
		val contentView = findViewById<TextView>(R.id.read_content)

		val helper = DBHelper(this)
		val db = helper.readableDatabase

		val cursor =
			db.rawQuery("select title, content from tb_memo order by _id desc limit 1", null)
		while (cursor.moveToNext()) {
			titleView.text = cursor.getString(0)
			contentView.text = cursor.getString(1)
		}
		db.close()
	}
}

📲 결과


📚 insert, update, delete, query

public long insert (String table, String nullColumnHack, ContentValues values)

public int update (String table, ContentValues values, String whereClause, String[] whereArgs)

public int delete (String table, String whereClause, String[] whereArgs)

public Cursor query (String table, String[] columns, String selection, String[] selectionArgs, 
	String groupBy, String having, String orderBy)
  • insert(), update(), delete(), query() 함수를 이용한 SQL 문 실행

  • SQL 문에 들어갈 부분을 매개변수로 대입하면 SQL 문을 만들어 실행시켜 주는 함수

execSql() 와 rawQuery() 함수의 경우에는 개발자가 직접 매개변수에 Sql 문을 작성해 주어야 한다.

하지만 insert(), update(), delete(), query() 함수를 사용하면 매개변수로 테이블과 Column, values 등 을 던져주면 알아서 Sql 문을 만들어 실행시켜 준다.

📌 ContentValues 클래스

val values = ContentValues()
values.put("name", "kotdev")
values.put("phone", "01044442322")
db.insert("USER_TB", null, values)
  • ContentValues 는 insert, update 를 위한 컬럼 데이터 집합 객체

  • Map 객체처럼 키-값 형태로 데이터 여러건을 ContentValues 에 등록

컬럼명을 Key로, 해당 컬럼에 들어갈 데이터를 Value로 준다.

📌 query()

table : select 하고자 하는 테이블 명

columns : 획득하고자 하는 column 명, 배열 데이터로 column 명 지정

selection : select 문의 where 뒤에 들어갈 문자열

selectionArgs : selection 에 지정된 문자열이 데이터가 들어갈 자리를 ? 로 표현했다면 ? 에 들어갈 데이터

groupBy : select 문의 group by 뒤에 들어갈 문자열

having : select 문의 having 조건

orderBy : select 문의 order by 조건
  • query() 에 들어가는 매개변수들

🧩 실습 예제

execSql() 와 rawQuery() 함수가 아닌, insert() 와 query() 를 사용해 작성 해보자!

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


    <EditText
        android:id="@+id/add_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="제목을 입력하세요"
        android:inputType="text" />


    <EditText
        android:id="@+id/add_content"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:hint="메모를 입력하세요"
        android:inputType="textMultiLine"
        android:scrollbars="vertical" />


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

</LinearLayout>


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

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="DB Select 결과" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Title : " />

        <TextView
            android:id="@+id/read_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Content : " />

        <TextView
            android:id="@+id/read_content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>

</LinearLayout>


  • DBHelper.xml
package com.kotdev99.android.c61

class DBHelper(context: Context?) :
	SQLiteOpenHelper(context, "memodb", null, 1) {
	override fun onCreate(db: SQLiteDatabase) {
		val memoSQL = ("create table tb_memo " +
				"(_id integer primary key autoincrement,"
				+ "title,"
				+ "content)")
		db.execSQL(memoSQL)
	}

	override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
		db.execSQL("drop table tb_memo")
		onCreate(db)
	}
}

  • MainActivity.kt
package com.kotdev99.android.c61

class MainActivity : AppCompatActivity() {
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_main)

		val titleView = findViewById<EditText>(R.id.add_title)
		val contentView = findViewById<EditText>(R.id.add_content)
		val addBtn = findViewById<Button>(R.id.add_btn)

		addBtn.setOnClickListener {
			val title = titleView.text.toString()
			val content = contentView.text.toString()

			val helper = DBHelper(this)
			val db = helper.writableDatabase

			val values = ContentValues()
			values.put("title", title)
			values.put("content", content)
			db.insert("tb_memo", null, values)
			db.close()

			val intent = Intent(this, ReadActivity::class.java)
			startActivity(intent)
		}
	}
}

  • ReadActivity.kt
package com.kotdev99.android.c61

class ReadActivity : AppCompatActivity() {
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_read)

		val titleView = findViewById<TextView>(R.id.read_title)
		val contentView = findViewById<TextView>(R.id.read_content)

		val db = DBHelper(this).readableDatabase
		val cursor = db.query(
			"tb_memo",
			arrayOf("title", "content"),
			null,
			null,
			null,
			null,
			"_id desc limit 1"
		)
		while (cursor.moveToNext()) {
			titleView.text = cursor.getString(0)
			contentView.text = cursor.getString(1)
		}
	}
}

📲 결과

결과는 동일하다.

profile
응애 나 아기 뉴비

0개의 댓글