이 때 1행 2열의 이름을 Key라고 하고 2행 2열의 김철수를 Value라고 합니다. 전화번호, 이메일, 주소도 역시 Key이고 그에 대응되는 값들도 역시 Value가 되겠죠. 순서라는 Key는 각 데이터에 대해 겹치지 않는 고유한 값이기 때문에 고유키(Primary Key)라고 합니다.
이렇게 Key와 Value가 1:1이나 1:n 또는 n:n 의 관계를 가지는 데이터베이스를 관계형 데이터베이스(Relational Database)라고 합니다.
SQL과 CRUD
위에 보여드린 DB에서 박모모의 전화번호를 찾는건 매우 간단합니다. 하지만 만약 행의 길이가 수만줄에 이르고 그 안에 박모모의 동명이인도 수백명이 존재하는 테이블이 있다면 어떨까요? 그 안에서 내가 원하는 박모모의 전화번호를 찾는건 쉽지 않은 일일겁니다. Structured Query Language (SQL)는 그런 복잡한 DB를 좀 더 용이하게 다루기 위해서 만들어진 언어입니다.
CRUD라는 표현을 들어보신적 있으실 겁니다. CRUD는 기본적인 데이터 처리 기능인 Create(생성), Read(읽기), Update(갱신), Delete(삭제)를 의미하는 말로, 데이터베이스를 다루기 위해 가장 기본이 되는 4가지의 명령을 묶어놓은 단어입니다. SQL은 다음과 같은 명령어를 통해 CRUD를 구현할 수 있습니다.
이름
조작
SQL
Create
생성
INSERT
Read(또는 Retrieve)
읽기(또는 인출)
SELECT
Update
갱신
UPDATE
Delete(또는 Destroy)
삭제(또는 파괴)
DELETE
SQL에서 실제 CRUD는 다음과 같이 구현하게 되는데요, 언어의 전체 문법은 SQL syntax 페이지에서 확인할 수 있습니다.
대량의 데이터를 다루는데 관계형 데이터베이스가 사용되고, SQL이라는 언어를 사용해 데이터베이스의 데이터를 다룬다는 걸 알았습니다. 이런 데이터베이스와 SQL을 결합해서, 사용할 수 있는 형태의 프로그램으로 만든 것을 Database Management System이라고 합니다. 거기에 관계형 데이터베이스를 사용했다면 RDBMS가 되겠죠.
그렇게 많고많은 DBMS중에 SQLite라는 것이 있습니다. 표준SQL을 지원하는 DB를 파일 하나로 구현한 경량 DBMS인데요, 운용시 리소스사용이 적고 무료로 사용할 수 있는 오픈소스이기 때문에 Android와 iOS에서 DB를 구현하기 위한 기본 라이브러리로 채택되어 있습니다.
처음엔 미해군의 구축함에서 이용하기 위해 만들어진 프로그램인데 공식 홈페이지에 따르면 현재는 전세계에서 1조개가 넘는 SQLite가 운용되고 있다고 하네요. SQLite에 대해 더 알고싶으신 분은 SQLite의 알려지지 않은 이야기를 읽어보시면 좋을 것 같습니다.
안드로이드의 SQLite 버전
상기한 이유로 안드로이드 API에는 SQLite가 기본 내장되어 있습니다. 복잡한 DB를 다룰 계획이 없다면 SQLite를 이용해 DB를 구축하는 것으로 충분합니다.
원시 SQL 쿼리에 관한 컴파일 시간 확인이 없습니다. 따라서 데이터 그래프가 변경됨에 따라 영향을 받는 SQL 쿼리를 수동으로 업데이트해야 합니다. 이 과정은 시간이 오래 걸리고 오류가 쉽게 발생할 수 있습니다.
SQL 쿼리와 데이터 객체 간에 변환하려면 많은 상용구 코드를 사용해야 합니다.
그 대신 구글에서는 SQLite를 더 안전하게 사용할 수 있는 SQLiteOpenHelper라는 헬퍼클래스를 제공하고 있습니다. 다음 파트에서는 이 SQLiteOpenHelper 클래스를 사용해서 CRUD작업을 하는 간단한 앱을 만들어보도록 하겠습니다.
Part B. 샘플 앱 작성
메인 레이아웃
New Project > Empty Activity를 선택해 빈 프로젝트를 하나 만들어줍니다.
그리고 앱에서 사용할 화면을 만듭니다. 전화번호부 DB를 상정할 것이므로 이름, 전화번호, 이메일 그리고 데이터의 Primary Key로 사용할 ID를 화면에서 입력할 수 있도록 EditText를 배치하겠습니다. 고유키는 데이터를 데이터베이스에 입력할 때 자동으로 부여되기 때문에 입력할 필요가 없지만 삭제, 업데이트를 테스트할 때 필요하기 때문입니다.
CRUD 테스트를 위해서 데이터를 입력하는 INSERT, 갱신하는 UPDATE, 삭제하는 DELETE 그리고 DB의 모든 내용을 표시하는 VIEW 버튼을 만들겠습니다. 작동에 대한 로그는 화면 아래쪽에 배치한 ScrollView에 표시되도록 하였습니다.
다음은 SQLiteOpenHelper 클래스를 상속하는 DatabaseHelper 클래스를 작성합니다. 이 앱에서 사용하게 될 데이터베이스의 프라이머리키는 _id이고 그 외에 name, phone, email 이라는 키를 갖고 있습니다. 데이터베이스의 파일명 및 그외 중복으로 써야하는 상수들을 companion object에 정의하고 상속에 필요한 생성자들을 전달해줍니다.
DatabaseHelper는 복수개의 인스턴스가 생성되어 DB에 동시접근할수 있는 문제를 방지하기 위해 싱글톤으로 작성합니다. constructor에 직접 접근하지 못하도록 private화 하고, getInstance에서 Double Checked Locking을 통해 인스턴스를 반환하는 싱글톤 구조를 구축합니다. 싱글톤에 대한 더 자세한 설명은 알기쉬운 Singleton Pattern 강의를 참고하세요.
다음은 DB버전이 변경되었을 때 동작하는 onUpgrade를 구현합니다. 여기서는 DB버전이 증가되었으면 기존 DB를 삭제하고 새로운 DB를 작성하는 단순한 구조로 정의했습니다.
1
2
3
4
5
6
overridefunonUpgrade(db:SQLiteDatabase?,oldVersion:Int,newVersion:Int){if(oldVersion!=newVersion){db?.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")onCreate(db)}}
CRUD 구현
Create
우선은 CRUD의 C를 구현하겠습니다. insertData에서는 name, phone, email을 파라메터로 받게 됩니다. writableDatabase로 데이터베이스를 오픈하고 외부에서 받아온 데이터를 contentValues로 묶어서 insert 명령으로 데이터베이스에 값을 추가합니다.
1
2
3
4
5
6
7
8
9
funinsertData(name:String,phone:String,email:String){valdb=this.writableDatabasevalcontentValues=ContentValues().apply{put(COL2_NAME,name)put(COL3_PHONE,phone)put(COL4_EMAIL,email)}db.insert(TABLE_NAME,null,contentValues)// 값이 없으면 행을 삽입하지않음
}
Update
update에서는 id로 선택해 준 데이터의 내용을 갱신합니다. insert에서처럼 writableDatabase로 데이터베이스를 오픈하고 contentValues를 넘겨주어 데이터를 갱신합니다. 이 때 "$COL1_ID = ?" 쿼리를 사용해 갱신할 데이터를 특정해주면 됩니다.
fungetAllData():String{varresult="No data in DB"valdb=this.readableDatabasevalcursor=db.rawQuery("SELECT * FROM $TABLE_NAME",null)try{if(cursor.count!=0){valstringBuffer=StringBuffer()while(cursor.moveToNext()){stringBuffer.append("ID :"+cursor.getInt(0).toString()+"\n")stringBuffer.append("NAME :"+cursor.getString(1)+"\n")stringBuffer.append("PHONE :"+cursor.getString(2)+"\n")stringBuffer.append("EMAIL :"+cursor.getString(3)+"\n\n ")}result=stringBuffer.toString()}}catch(e:Exception){e.printStackTrace()}finally{if(cursor!=null&&!cursor.isClosed){cursor.close()}}returnresult}
MainActivity 준비
뷰 바인딩을 적용한 메인액티비티를 작성합니다. dbHelper 인스턴스를 생성하고, 사용이 끝나면 onDestroy에서 close()로 리소스를 반환하도록 하였습니다.
각 버튼을 클릭했을 때 부여할 기능을 정의합니다. 값을 입력하는 insertDb, 갱신하는 updateDb, 삭제하는 deleteDb, 그리고 조회하는 getAllDb함수를 만듭니다. 작업은 try 블록 안에서 실행하며 에디트텍스트에 입력한 값을 dbHelper에 정의한 각 함수로 전달합니다. 이 때 입력되는 sql 쿼리에 공백이 있으면 안되기 때문에 trim으로 공백을 제거하도록 했습니다.