본문 바로가기
Xcode/Swift - PlayGround

PlayGround) SQLite를 이용하여 데이터를 저장하자!

by 후르륵짭짭 2020. 7. 18.
728x90
반응형

안녕하세요 후르륵짭짭 입니다.

이번에는 SQLite에 대해서 알아보려고 합니다.

사실 저도 SQLite는 잘 알지 못하고 최근에 공부 해본 것이라 부족한 점이 많습니다

 

Sqlite는 mysql와 같은 데이터베이스 문법을 사용합니다. 아마 살짝 다른 부분이 있겠지만, 아직은 자세히 알지 못하고

공부한 내용만 남아 보도록 하겠습니다.

 

Sqlite는 CoreData와 같이 아이폰이나 아이패드 내부에 데이터를 저장할 수 있습니다.

예전에는 sqlite는 외부 라이버리를 사용하였는데 Swift5 부터는 내장이 되어 있는 것 같아서 둘다 

따라서 둘다 외부 라이브러리 사용 없이 사용이 가능합니다.

 

하지만 CoreData는 제 기분에서 좀더 쉽게 테이블 구성을 볼 수 있고 스프링의 JPA 같은 객체지향적은 데이터 베이스인 느낌을 많이 받았습니다. 반면 Sqlite는 mysql 처럼 관계형 데이터 베이스 처럼 명령어를 적고 그에 따라 수행하는 경향이 강했습니다.

그럼 내용을 다뤄 보도록 하겠습니다.

 

** Create DB **

import SQLite3

이렇게 SQLite3를 임포트 해주시고

class DBHelper {
    
    static let shared = DBHelper()
    
    var db : OpaquePointer?
    var path = "mySqlite.sqlite"
    
    init(){
        self.db = createDB()
    }
    
    func createDB() -> OpaquePointer? {
        
        var db : OpaquePointer? = nil
        do {
        
        //appendingPathExtension(path)은 플레이그라운드에서는 되지만 실제 APP에서는 되지 않았다.
        //따라서 이렇게 변경해줘야한다. appendingPathExtension(path) -> appendingPathComponent(path)
        let filePath = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent(path)
            
            if sqlite3_open(filePath.path, &db) == SQLITE_OK {
                print("Succesfully create Database path : \(filePath.path)")
                return db
            }
            
        }
        catch{
            print("ERROR in CreateDB - \(error.localizedDescription)")
        }
        
        print("ERROR in CreateDB - sqlite3_open ")
        return nil
        
    }

 

- 설명 -

//	sqlite는 C언어로 구현되어 있는 것을 swift 형식으로 해준 것 입니다
//  따라서 아래 처럼 OpaquePointer로 해줘야 하는데
//  이는 C의 Pointer와 같은 것 입니다.

    var db : OpaquePointer?
    
    
//  이것은 데이터 베이스의 이름을 어떻게 할 것인지 정해주는 것 입니다.
//  반드시 "데이터베이스 이름".sqlite 로 적어줘야합니다.

    var path = "mySqlite.sqlite"

    
    func createDB() -> OpaquePointer? {
        
        //C포인터를 비어 있음으로 해줍니다. 어차피 갱신 될 겁니다.
        var db : OpaquePointer? = nil
        
        do {
         
         //이렇게 데이터베이스의 파일 경로를 지정해줍니다. url 부분은 저도 잘 몰라서 패스 하도록 하겠습니다.
         //대신 마지막 부분에 appending을 통해서 위에서 설정한 경로를 넣어주세요
        let filePath = try FileManager
        		.default
                .url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
                .appendingPathExtension(path)
            
            //sqlite3_open은 파일경로에 있는 데이터 베이스와 연결 하겠다.
            //만약에 이미 존재한다면 다시 생성하지 않고 넘어갑니다.
            //그리고 SQLITE_OK는 타당함이 확인 해주는 부분 입니다
            if sqlite3_open(filePath.path, &db) == SQLITE_OK {
                print("Succesfully create Database path : \(filePath.path)")
                return db
            }
            
        }
        catch{
            print("ERROR in CreateDB - \(error.localizedDescription)")
        }
        
        print("ERROR in CreateDB - sqlite3_open ")
        return nil
        
    }
    

 

** Create Table **

    func createTable(){
        //AUTOINCREMENT를 사용하기 위해서는 INT 가 아니라 INTEGER을 사용해야 한다.
        let query = "create table if not exists myDB(id INTEGER primary key autoincrement, name text not null, info text not null);"
        
        var statement : OpaquePointer? = nil
        
        if sqlite3_prepare_v2(self.db, query, -1, &statement, nil) == SQLITE_OK {
            if sqlite3_step(statement) == SQLITE_DONE {
                print("Create Table SuccessFully \(String(describing: db))")
            }
            else{
                let errorMessage = String(cString: sqlite3_errmsg(db))
                print("\n Create Table step fail :  \(errorMessage)")
            }
        }
        else{
            let errorMessage = String(cString: sqlite3_errmsg(db))
            print("\n create Table sqlite3_prepare Fail ! :\(errorMessage)")
            
        }

        sqlite3_finalize(statement)
    }

 

- 설명 -

 
        //AUTOINCREMENT를 사용하기 위해서는 INT 가 아니라 INTEGER을 사용해야 한다.
        //일반적인 mysql 문법을 사용할 수 있습니다.
        //그런데 테이블 중복 생성 방지를 위해서 if not exists를 적어주는게 좋고
        //정수는 가급적이면 integer로 문자열은 text로 해주는게 좋을 것 같습니다.
        let query = "create table if not exists myDB(id INTEGER primary key autoincrement, name text not null, info text not null);"
        
        //이 부분은 위의 명령을 수행하게 될 DB의 C포인터 위치의 주소를 담는 공간입니다.
        var statement : OpaquePointer? = nil
        
 
		
        
		//sqlite3_prepare_v2는 sqlite의 명령어를 수행하는 모든 부분에 수행하게 됩니다.
		//위에서 생성한 DB의 주소에 query(명령어)를 어디까지 읽을 것인지 알려주는 -1(전체를 읽는다)로 해주고
        //그 명령어를 수행하는 DB의 주소를 statement에 넣게 됩니다. 마지막 부분은 잘 모르겠습니다
        //그리고 확인 떨어지면 sqlite_step을 통해 명령 수행 완료 여부를 확인합니다.
        if sqlite3_prepare_v2(self.db, query, -1, &statement, nil) == SQLITE_OK {
            if sqlite3_step(statement) == SQLITE_DONE {
                print("Create Table SuccessFully \(String(describing: db))")
            }
            else{
            //	sqlite에서 발생한 에러 메시지를 담게 됩니다.
                let errorMessage = String(cString: sqlite3_errmsg(db))
                print("\n Create Table step fail :  \(errorMessage)")
            }
        }
        else{
            let errorMessage = String(cString: sqlite3_errmsg(db))
            print("\n create Table sqlite3_prepare Fail ! :\(errorMessage)")
            
        }
  
//sqlite3를 수행하면서 생긴 메모리를 제거해줍니다.
sqlite3_finalize(statement)

 

만약에 저 처럼 구조체 까지 sql에 넣고 싶은 경우는 아래 처럼 Codable로 만들어 주세요

struct Info : Codable{

    let grade : String
    let studentID : Int
    let general : String
    
}

 

** Delete table **

위의 Create table과 동일하지만 명령어만 차이가 납니다. (명령어는 mysql의 테이블 삭제 명령어랑 동일 합니다)

    func deleteTable(){
        let query = "DROP TABLE myDB"
        
        var statement : OpaquePointer? = nil
        
        if sqlite3_prepare_v2(self.db, query, -1, &statement, nil) == SQLITE_OK {
            if sqlite3_step(statement) == SQLITE_DONE {
                print("Delete Table SuccessFully \(String(describing: db))")
            }
            else{
                let errorMessage = String(cString: sqlite3_errmsg(db))
                print("\n Delete Table step fail ! : \(errorMessage)")
                
            }
        }
        else{
            let errorMessage = String(cString: sqlite3_errmsg(db))
            print("\n delete Table prepare fail! : \(errorMessage)")
            
        }
        
        sqlite3_finalize(statement)
    }
    

 

** Insert Data **

    func insertData(name : String, studentInfo: Info ){
        
        //autocrement일 경우에는 입력 부분에서는 컬럼을 추가 안해줘도 자동으로 추가가 되지만
        //쿼리 문에서는 이렇게 추가 해줘야합니다.
        let query = "insert into myDB (id, name, info) values (?,?,?);"
        
        var statement : OpaquePointer? = nil
        
        do {
            //이렇게 데이터를 인코등 해주고 그 데이터를 String으로 변형 해준다.
            //왜냐하면 bind 해줄 때 data 타입이 없기 때문이다.
        let data = try JSONEncoder().encode(studentInfo)
        let dataToString = String(data: data, encoding: .utf8)
        
            print(dataToString!)
            
        if sqlite3_prepare_v2(self.db, query, -1, &statement, nil) == SQLITE_OK {
            //insert는 read와 다르게 컬럼의 순서의 시작을 1 부터 한다.
            //따라서 id가 없기 때문에 2로 시작한다.
            sqlite3_bind_text(statement, 2, NSString(string: name).utf8String , -1, nil)
            sqlite3_bind_text(statement, 3, NSString(string: dataToString!).utf8String , -1, nil)
            
            if sqlite3_step(statement) == SQLITE_DONE {
                print("Insert data SuccessFully : \(String(describing: db))")
            }
            else{
                let errorMessage = String(cString: sqlite3_errmsg(db))
                print("\n insert Data sqlite3 step fail! : \(errorMessage)")
            }
        }
        else{
            let errorMessage = String(cString: sqlite3_errmsg(db))
            print("\n insert Data prepare fail! : \(errorMessage)")
        }
            
        
        sqlite3_finalize(statement)
            
        }
        catch{
            print("JSONEncoder Error : \(error.localizedDescription)")
        }
    }

 

- 설명 -

            //이렇게 데이터를 인코딩 해주고 그 데이터를 String으로 변형 해준다.
            //왜냐하면 bind 해줄 때 data 타입이 없기 때문이다.
            //json 구조는 인코딩이 사실 필수 이다.
        let data = try JSONEncoder().encode(studentInfo)
        let dataToString = String(data: data, encoding: .utf8)
        
            print(dataToString!)
            
        if sqlite3_prepare_v2(self.db, query, -1, &statement, nil) == SQLITE_OK {
            //insert는 read와 다르게 컬럼의 순서의 시작을 1 부터 한다.
            //따라서 id가 없기 때문에 2로 시작한다.
            sqlite3_bind_text(statement, 2, NSString(string: name).utf8String , -1, nil)
			//sqlite는 0bj-C언어로 구현되어 있기 때문에 text를 넣기 위해서는 NSString으로 변경하고
            //utf8String으로 인코딩 해줘야하니다.
            sqlite3_bind_text(statement, 3, NSString(string: dataToString!).utf8String , -1, nil)
            
            //만약에 정수를 넣고 싶다면 아래 처럼 해줘여한다.
            sqlite3_bind_int(statement,4, Int32(id))
            

 

** read Data ** 

    func readData() {
        
        let query = "select * from myDB;"
        
        var statement : OpaquePointer? = nil
        
        if sqlite3_prepare_v2(self.db, query, -1, &statement, nil) == SQLITE_OK {
            
            while sqlite3_step(statement) == SQLITE_ROW{
                
                let id = sqlite3_column_int(statement, 0)
                
                
                //만약에 컬럼이 name 하나 뿐이 였다면 반환되는 결과물도 name 하나 뿐이기 때문에
                //이 부분이 1이 아니라 0이 되어야 한다.
                let name =  String(cString: sqlite3_column_text(statement, 1))
                let studentInfo = String(cString: sqlite3_column_text(statement, 2))
                
                do{
                    //sql에 data 타입이 아니라 String 타입으로 저장이 되어 있기 때문에, 반드시 String 타입을 data 타입으로 변경해서 디코드 해줘야한다.
                    let data = try JSONDecoder().decode(Info.self, from: studentInfo.data(using: .utf8)!)
                    print("readData Result : \(id) \(name) \(data)")
                }
                catch{
                    print("JSONDecoder Error : \(error.localizedDescription)")
                }
                
                
            }
            
        }
        else {
            let errorMessage = String(cString: sqlite3_errmsg(db))
            print("\n read Data prepare fail! : \(errorMessage)")
        }
        
        sqlite3_finalize(statement)
        
    }

 

-- 설명 --

//현재 테이블에서 컬럼이 존재하면 계속 읽는다 (Sqlite_Row)
            while sqlite3_step(statement) == SQLITE_ROW{
                
                //정수형 컬럼을 가져올 때 사용한다. 뒤에 0은 첫번째 컬럼을 뜻한다.
                let id = sqlite3_column_int(statement, 0)
                
                //만약에 컬럼이 name 하나 뿐이 였다면 반환되는 결과물도 name 하나 뿐이기 때문에
                //이 부분이 1이 아니라 0이 되어야 한다.
                let name =  String(cString: sqlite3_column_text(statement, 1))
                
                //문자열이라면 이렇게 text형식으로 가져온다. 우리는 JSON으로 인코딩 해서 String 형태로
                //넣어줬기 때문에 이렇게 받아와야한다.
                let studentInfo = String(cString: sqlite3_column_text(statement, 2))
                
                do{
                    //sql에 data 타입이 아니라 String 타입으로 저장이 되어 있기 때문에, 
                    //반드시 String 타입을 data 타입으로 변경해서 디코드 해줘야한다.
                    let data = try JSONDecoder().decode(Info.self, from: studentInfo.data(using: .utf8)!)
                    print("readData Result : \(id) \(name) \(data)")
                }
                catch{
                    print("JSONDecoder Error : \(error.localizedDescription)")
                }
                
                
            }

 

만약에 최상단의 컬럼만 가져오고 싶다면 아래처럼 해주면 된다.

//            if sqlite3_step(statement) == SQLITE_ROW{
//                let id = sqlite3_column_int(statement, 0)
//                               let name =  String(cString: sqlite3_column_text(statement, 1))
//
//                               print("readData Result : \(id) \(name)")
//            }
//            else{
//                let errorMessage = String(cString: sqlite3_errmsg(db))
//                print("\n insert Data prepare fail! : \(errorMessage)")
//            }

 

** Delete Data **

    func deleteData(){
        let query = "delete from myDB where id >= 2;"
        
        var statement : OpaquePointer? = nil
        
        if sqlite3_prepare_v2(self.db, query, -1, &statement, nil) == SQLITE_OK {
            
            if sqlite3_step(statement) == SQLITE_DONE {
                 print("Delete data SuccessFully : \(String(describing: db))")
            }
            else{
                let errorMessage = String(cString: sqlite3_errmsg(db))
                print("\n delete Data prepare fail! : \(errorMessage)")
            }
            
        }
        else{
            let errorMessage = String(cString: sqlite3_errmsg(db))
            print("\n delete Data prepare fail! : \(errorMessage)")
        }
        
        sqlite3_finalize(statement)
    }

 

** Update Data **

    func updateData(){
        
        let query = "update myDB set id = 2 where id = 5;"
        
        var statement : OpaquePointer? = nil
        
        if sqlite3_prepare_v2(self.db, query, -1, &statement, nil) == SQLITE_OK {
            
            if sqlite3_step(statement) == SQLITE_DONE {
                 print("Update data SuccessFully : \(String(describing: db))")
            }
            else{
                let errorMessage = String(cString: sqlite3_errmsg(db))
                print("\n delete Data prepare fail! : \(errorMessage)")
            }
            
        }
        else{
            let errorMessage = String(cString: sqlite3_errmsg(db))
            print("\n delete Data prepare fail! : \(errorMessage)")
        }
        
        sqlite3_finalize(statement)
    }
    

 

** 결과 호출 방법 **

//싱글톤으로 이용하는 방법
let myDB = DBHelper.shared

//let myDB = DBHelper()
//myDB.insertData(name: "ChapChap", studentInfo: Info(grade: "B+", studentID: 2020, general: "haha"))
//myDB.updateData()
myDB.readData()
//myDB.deleteData()
//myDB.deleteTable()
//myDB.disConectDB()

 

지금 까지 허접하지만 sqlite에 대해서 알아 봤습니다.

모두 모두 즐코코코코 하세욧!

 

www.raywenderlich.com/6620276-sqlite-with-swift-tutorial-getting-started

 

SQLite With Swift Tutorial: Getting Started

In this SQLite with Swift tutorial, you’ll learn to use a SQLite database with Swift projects by creating tables and inserting, updating and deleting rows.

www.raywenderlich.com

medium.com/@imbilalhassan/saving-data-in-sqlite-db-in-ios-using-swift-4-76b743d3ce0e

 

Saving data in SQLite DB in iOS using swift 4

Create a single view application in Xcode

medium.com

 

728x90
반응형

댓글