2023.02.28 - 안드로이드 앱개발자 과정

CHA·2023년 3월 1일
0

Android



영구적인 Data 저장

만일 우리가 휴대폰 전원을 껐을때, 우리가 저장해두었던 모든 Data 들이 없어진다면 어떨까요? 아주아주 불편한 일이 될겁니다. 휴대폰 전원을 끄던, 앱을 종료하던 우리가 만들어두었던 데이터들은 없어지면 안되겠죠. 그래서 이번에는 우리에게 필요한 Data 가 날아가지 않도록 영구적으로 Data 를 저장하는 방법에 대해 알아보겠습니다.


파일 입출력을 통한 Data 저장

우리가 엑셀이나 여러 문서작업들을 끝내고 프로그램을 종료하려하면 항상 저장을 할것인지의 여부를 물어봅니다. 그리고 저장을 한다고 하면, 엑셀 파일, 한글 파일 등의 파일형태 로 저장을 시켜주죠. 즉, 우리가 데이터를 저장하고자 한다면 파일 입출력 과정을 거쳐 파일에 우리의 데이터를 저장시켜주어야 합니다.

파일 입출력을 통한 데이터 저장의 방법으로 두가지를 알아봅시다. 하나는 디바이스의 내부 저장소에 저장을 하는 방법과 두번째는 외부 저장소, 즉 내장 메모리 혹은 SD 카드, usb 등에 저장하는 방법입니다.

Internal Storage (내부 저장소)

안드로이드는 데이터 관리에 굉장히 강력한 제한을 걸고 있습니다. 그래서 내부 저장소에 데이터를 저장하면 다른 앱에서 접근을 할 수 없도록 만들어두었습니다. 즉, 이 내부저장소에 접근하기 위해서는 앱 내부에서만 접근이 가능합니다. 또한 내부 저장소에 저장을 하기 위한 전용파일 이 있는데, 그 경로는 다음과 같습니다.

data/data/앱 전용폴더(패키지명)/files

내부 저장소에 저장이 되는 모든 데이터들은 위 경로에 저장이 됩니다. 그리고 앱이 삭제가 된다면 files 또한 삭제 됩니다. 추가적으로 안드로이드에서는 이 내부저장소에 10MB 이하의 데이터들만 저장할 것을 권장합니다. 크기가 크거나 복잡한 데이터들은 데이터베이스에 따로 저장을 시켜주어야 합니다.

그러면 이제 내부 저장소에 어떠한 방식으로 저장할 수 있는지 알아봅시다. EditText 에 우리가 원하는 정보를 저장하고 버튼을 누르면 내부 저장소에 저장이 되는 예제 입니다. xml 은 간단하니, 자바 코드만 보고 넘어갑시다. onCreate() 메소드 내부에는 버튼 객체 참조와 리스너 작업만 있으므로, 따로 작성하지 않았습니다.

아래 clickSave() 는 Save 버튼을 눌렀을 때 동작하는 메서드 입니다.

public void clickSave(){
    String data = et.getText().toString();
    et.setText("");

    try {
        FileOutputStream fos = openFileOutput("Data.txt",MODE_APPEND);
        PrintWriter writer = new PrintWriter(fos);

        writer.println(data);
        writer.flush();
        writer.close();
        
        tv.setText("Saved");
        
    } catch (FileNotFoundException e) {
        throw new RuntimeException(e);
    }
}
  • String data = et.getText().toString();et.setText(""); 은 EditText 를 이용해 데이터를 String 변수에 담고, EditText 를 초기화 하는 작업입니다.

  • FileOutputStream fos = openFileOutput("Data.txt",MODE_APPEND);
    앞서 보았던 내부저장소의 경로에 Data.txt 파일을 하나 만들고, 그 정보를 FileOutputStream 객체에 담는 작업입니다. MODE_APPEND 는 이 파일이 덧붙이기로 데이터를 저장함을 명시합니다. 또한 이 작업은 예외상황이 발생할 여지가 있기 때문에 try-catch 문을 통해 예외처리를 해주어야 합니다.

  • PrintWriter writer = new PrintWriter(fos);PrintWriter 객체의 생성자에 FileOutputStream 객체를 전달하여 데이터를 저장할 준비를 마칩니다. fos 객체는 바이트 단위로 데이터를 저장합니다. 그리고 불편하죠. 그래서 바이트 스트림이 아닌 문자스트림으로 데이터를 저장할 수 있도록 도와주는게 PrintWriter 클래스 입니다.

  • writer.println(data); 을 이용하여 앞서 입력했던 datafile 폴더안에 Data.txt 파일에 저장해주었습니다. writer.flush(); 을 이용하면 버퍼안에 남아있는 데이터 찌꺼기들을 비워줄 수 있습니다. 데이터를 저장하면 flush() 는 항상 해주는게 좋다고 생각합시다. 마지막으로 writer.close() 를 이용하여 열었던 스트림을 닫아주어야 합니다.

  • tv.setText("Saved") 를 이용하면 TextView 에 Saved 라는 문자가 출력됩니다. 이 코드까지 왔다는 이야기는 예외상황 없이 모든 데이터가 잘 저장되었다는 의미입니다.

이제 데이터의 저장방법은 알아보았으니, 저장된 데이터를 불러와 TextView 에 뿌려주는 작업을 해봅시다. 아래 clickLoad() 는 Load 버튼을 눌렀을 때 동작하는 메서드 입니다.

public void clickLoad(){
    try {
        FileInputStream fis = openFileInput("Data.txt");
        InputStreamReader isr = new InputStreamReader(fis);
        BufferedReader reader = new BufferedReader(isr);
        StringBuffer buffer = new StringBuffer();

        while(true){
            String line = reader.readLine();
            if(line == null) break;

            buffer.append(line+"\n");
        }

        tv.setText(buffer.toString());

    } catch (FileNotFoundException e) {
        Toast.makeText(this, "해당 파일을 찾을 수 없습니다", Toast.LENGTH_SHORT).show();
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
  • FileInputStream fis = openFileInput("Data.txt"); 을 이용하여 데이터를 불러올 파일을 찾는 작업을 해줍시다. 앞선 예제에서 만들었던 Data.txt 를 찾아올 예정이며, 이 부분도 역시 예외상황이 발생할 수 있으므로, try-catch 문을 이용해 예외 처리를 해줍시다.

  • InputStreamReader isr = new InputStreamReader(fis); 을 이용하여 생성자 파라미터로 FileInputStream 객체를 전달하여 바이트 스트림을 문자 스트림으로 바꿔주어야 합니다. 데이터를 저장했을 때, 즉 FileOutputStream 을 이용할 때에는 바로 보조 문자 스트림을 사용할 수 있었으나, FileInputStream 을 이용할 때에는 보조 문자 스트림을 사용하기 위한 작업이 별도로 필요합니다.

  • BufferedReader reader = new BufferedReader(isr); 을 이용하여 문자열을 읽어 들입시다.

  • StringBuffer buffer = new StringBuffer(); 을 이용하여 데이터를 임시적으로 저장할 버퍼를 생성합니다. 원래는 ArrayList 와 같은 컬렉션을 이용하는게 일반적이나, 연습이므로 버퍼를 사용합시다.

  • String line = reader.readLine(); 을 이용하여 Data.txt 에 저장된 데이터를 한줄씩 읽어와 문자열 변수 line 에 저장합니다. readLine() 의 경우 줄바꿈 문자는 제외하고 읽어온다는것을 알아둡시다. 또한 이 코드에서도 예외상황이 발생할 수 있으므로 catch 문을 add 해줍시다.

  • if(line == null) break; 을 이용하여 더 읽어올 데이터가 있는지 판단합니다. line 변수가 가지고 있는 값이 null 이라면, 더 이상 읽어올 데이터가 없다는 의미 이므로, break 를 해주어 while 문을 종료합니다.

  • buffer.append(line+"\n"); 을 이용하여 버퍼에 가져온 데이터를 저장해둡니다. readLine() 이 줄바꿈 문자는 제외하고 읽어오므로 데이터를 보기 좋게 정렬하기 위해서는 여기에 줄바꿈 문자를 더해 저장합시다.

  • tv.setText(buffer.toString()); , 이 코드까지 왔다는 이야기는 더 이상 읽어올 데이터가 없어 while 문이 종료된 이후이기 때문에 지금까지 읽어온 데이터를 TextView 에 뿌려줍니다.

이렇게 해서 내부저장소에 어떠한 방식으로 데이터가 저장되며, 읽어올 수 있는지 알아보았습니다. 이제 외부저장소를 이용한 데이터 저장을 알아봅시다.


External Storage (외부 저장소)

외부 저장소의 종류는 3가지 입니다. 내장메모리, SD 카드, USB 입니다. 그리고 보통은 SD 카드를 이용하는 경우가 많습니다. 안드로이드 API 버전 29 이후로 외부 저장소를 이용한 개발 방법이 완전히 달라졌습니다. 이유는 미디어 파일의 관리 때문인데요, API 29 버전 이전의 외부저장소를 Legacy Storage , 이후의 외부저장소를 Scoped Storage 라고 합니다.

Legacy Storage 에서는 데이터의 저장 공간을 공용공간개별공간 으로 나누었습니다. 공용공간은 앱을 삭제하더래도 데이터는 남아있는 공간이며, 개별공간은 내부 저장소와 같이 앱을 삭제하면 사라지는 공간입니다. 다만, 이 공용공간이 Scoped Storage 에서는 MediaNot Media 로 구분되었습니다. 그래서 이 부분에 관한 내용은 이후 미디어에 관한 내용을 다룰 때 다시 한번 다뤄보겠습니다. 이번 예제에서는 외부 저장소의 개별공간에 데이터를 저장하고 불러오는 방법을 알아봅시다.

앞의 내부저장소의 예제와 동일하게 Save 버튼을 누르면 만든 데이터를 외부저장소에 저장하며, Load 버튼을 누르면 외부저장소에 저장된 데이터를 불러오는 예제 입니다. 자바 코드만 봅시다.

아래 clickSave() 는 Save 버튼을 눌렀을 때 동작하는 메서드 입니다.

public void clickSave(){
    String state = Environment.getExternalStorageState();

    if(!state.equals(Environment.MEDIA_MOUNTED)){
        Snackbar.make(tv,"SD card is not mounted",Snackbar.LENGTH_SHORT).show();
        return;
    }

    String data = et.getText().toString();
    et.setText("");
    
    File[] dirs = getExternalFilesDirs("Hello");
    File file = new File(dirs[0],"Data.txt");

    try {
        FileWriter fw = new FileWriter(file,true);
        PrintWriter writer = new PrintWriter(fw);
        writer.println(data);
        writer.flush();
        writer.close();

        tv.setText("Saved");
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
  • String state = Environment.getExternalStorageState(); 을 이용하여 외부 저장소를 사용할 수 있는지의 상태를 받아옵시다. 그 상태값을 state 문자열변수에 담아두었습니다.

  • if(!state.equals(Environment.MEDIA_MOUNTED)) 을 이용해 외부 저장소의 사용 가능 여부를 판단합니다. 외부 저장소를 읽고 쓸 수 있는 상태는 MEDIA_MOUNTED 뿐이므로, if 문을 활용하여state 에 담긴 상태값이 MEDIA_MOUNTED 와 동일한지 판단합니다. 동일하지 않다면, 사용할 수 없는 상태이므로 스낵바 하나를 띄워주고 메서드를 종료합니다.

  • String data = et.getText().toString();et.setText(""); 의 코드까지 왔다면 외부저장소가 사용가능한 상태이므로 저장할 데이터를 받아오는 작업을 합시다.

내부저장소에서는 Activity 가 직접 스트림을 곧바로 열어주었습니다. 다만, 외부저장소의 경우 스트림을 곧바로 열어주는 기능이 없기 때문에 우리가 직접 스트림을 열어주어야 합니다. 그리고 스트림을 열기위해서는 File 객체가 필요하므로 객체부터 만들어 봅시다.

  • File[] dirs = getExternalFilesDirs("Hello"); 을 이용하면 우리가 사용할 수 있는 외부저장소의 파일 배열 객체가 반환 됩니다. 그 배열 객체를 dirs 객체에 담아두었습니다. 그리고 이 배열의 첫번째 요소에는 보통 기본 외부 저장소가 오게 됩니다.

  • File file = new File(dirs[0],"Data.txt"); 을 이용하여 파일 객체를 만듭니다. 객체의 생성자로 어떤 외부 저장소를 이용할것인지와 어떤 파일에 저장할 것인지에 대한 정보를 전달합니다.

  • FileWriter fw = new FileWriter(file,true); 을 이용하면 file 과 연결하는 스트림을 만들수 있으며 다행히도 바이트 스트림이 아닌 문자스트림으로 곧바로 생성이 가능합니다. 그리고 두번째 파라미터의 boolean 값은 append 모드의 여부를 전달합니다. true 라면 파일 저장시 덧붙이기 모드로 작성이 가능하며, false라면 덮어쓰기 모드로 동작합니다.

  • PrintWriter writer = new PrintWriter(fw); 을 이용하연 이제 파일에 데이터를 저장할 준비가 완료됩니다.

추가로, 데이터를 저장할 외부 저장소를 dir[0] 으로 지정해주었습니다. 만일 다른 저장소에 저장하고 싶다면 배열의 인덱스 값을 조정해주어야 하는데, 기기 마다 저장소의 배열 위치가 다를 수 있으므로 아래 코드와 같이 입력하여 저장소의 path 값을 확인해준 후 사용해줍시다.

File[] dirs = getExternalFilesDirs("Hello"); 
String s = dirs[0].getPath();
for(File f : dirs){
    s += f.getPath() + "\n";
}
tv.setText(s);


위 사진에서 볼 수 있듯 두개의 path 값을 볼 수 있습니다. 위쪽에 먼저 나온 path 값은 sd 카드의 경로이며, 두번째는 내장 메모리의 path 값입니다. 보통 파일 배열 객체의 첫번째 요소로는 sd 카드가 들어갑니다.

나머지는 앞선 내부 저장소 예제와 동일하기에 넘어가겠습니다.

이제 데이터를 저장하는 방법은 알았으니, 데이터를 불러오는 방법을 알아봅시다. 아래 clickLoad() 는 Load 버튼을 눌렀을 때 동작하는 메서드 입니다.

public void clickLoad(){
    String state = Environment.getExternalStorageState();
    if(state.equals(Environment.MEDIA_MOUNTED) || state.equals(Environment.MEDIA_MOUNTED_READ_ONLY)){
        File[] dirs = getExternalFilesDirs("MyDir");
        File file = new File(dirs[0],"Data.txt");

        try {
            FileReader fr = new FileReader(file);
            BufferedReader reader = new BufferedReader(fr);

            StringBuffer buffer = new StringBuffer();
            while(true){

                String line = reader.readLine();
                if(line == null) break;
                buffer.append(line + "\n");
            }

            tv.setText(buffer.toString());
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

데이터를 읽어오는 작업은 내부 저장소의 작업과 크게 다른점이 없습니다. 다만, 외부 저장소가 읽어올 수 있는 상태인지만 체크하고 데이터를 읽어오면 됩니다.

  • String state = Environment.getExternalStorageState(); 외부 저장소의 상태값을 state 문자열 변수에 담아둡시다.

  • 외부 저장소의 상태가 읽어올수 있는 상태이거나 읽고 쓸 수 있는 상태라면 데이터를 불러오는것이 가능하니, state.equals(Environment.MEDIA_MOUNTED) || state.equals(Environment.MEDIA_MOUNTED_READ_ONLY) 을 if 문의 조건으로 앞의 코드를 작성해주면, 외부 저장소의 접근이 가능한 상태임을 의미합니다.

  • File[] dirs = getExternalFilesDirs("MyDir");File file = new File(dirs[0],"Data.txt"); 을 이용하여 파일 객체를 만듭니다. 데이터를 불러오는 작업 역시 File 객체가 필요하므로 File 객체를 만들어 줍시다.

  • FileReader fr = new FileReader(file);BufferedReader reader = new BufferedReader(fr); 이용하여 데이터를 읽어올 준비가 끝났습니다.

나머지는 내부 저장소에서 데이터를 읽어오는 작업과 동일하므로, 넘어 갑시다.


Shared Preference

그리고 Data 를 저장하기 위해서는 익히 알고있듯, 파일의 형태로 저장해주어야 합니다. 그래서 파일입출력에 관한 내용을 숙지하고 있어야 합니다. 다만, 파일 입출력의 경우 그 과정이 좀 복잡합니다. 특히 단순값들을 저장하기 위해서 파일입출력을 사용하는건 비효율적이죠. 그래서 나온 개념이 SharedPreference 입니다.

SharedPreference 객체는 키-값 쌍이 포함된 파일을 가리키며 키-값 쌍을 읽고 쓸 수 있는 간단한 메서드를 제공합니다. 그래서 앞선 두가지 방법보다 쉽게 저장이 가능하나, 많은 데이터를 처리하기에는 적절하지 않습니다. 보통 SharedPreference API 는 설정값을 저장할 때 많이 사용합니다. 그리고 설정값들을 저장해놓아야 하기 때문에 내부저장소에 저장됩니다.

getSharedPreference() 이나 getPreference() 중 하나를 호출하여 새로운 파일을 생성하거나 기존 파일에 엑세스 할 수 있습니다. getPreference() 는 하나의 Activity 에 관한 설정값을 변경할 때 사용합니다. 반면에 getSharedPreference() 은 앱 전체의 설정에 관여하고자 할 때 사용하는 메서드 입니다. 보통 Activity 단위로 설정을 다루지는 않기에 후자가 많이 사용됩니다.

그러면 이제 예제를 통해 SharedPreference 을 알아봅시다. EditText 2개를 통해 데이터를 입력받고, Save 버튼과 Load 버튼을 누르면 데이터 값이 저장되는 예제입니다. 자바 코드만 봅시다.

아래 코드는 Save 버튼 눌렀을 때 실행되는 코드 입니다.

public void clickSave(){
    String name = etName.getText().toString();
    int age = Integer.parseInt(etAge.getText().toString());

    SharedPreferences pref = getSharedPreferences("Data",MODE_PRIVATE); 
    
    SharedPreferences.Editor editor = pref.edit();
    editor.putString("name",name);
    editor.putInt("age",age);

    editor.commit();
    tv.setText("저장 완료");
}
  • String name = etName.getText().toString();int age = Integer.parseInt(etAge.getText().toString()); 를 이용하여 입력받은 데이터들을 각각의 변수에 담아둡니다.

  • SharedPreferences pref = getSharedPreferences("Data",MODE_PRIVATE); : SharedPreference 의 객체는 키-값이 포함되는 파일을 가리킵니다. 그렇기 때문에 xml 파일에 데이터를 저장시켜주어야 하며, xml 파일의 이름을 확장자 없이 파라미터로 전달합니다. MODE_PRIVATE 의 경우 덮어쓰기 모드입니다. SharedPreference 을 사용할 때에는 확정값이라고 생각하고 다른 모드값은 생각하지 맙시다.

  • SharedPreferences.Editor editor = pref.edit(); : SharedPreferences 을 사용할 때 데이터의 저장을 도와주는 객체가 Editor 객체 입니다. pref.edit() 로 객체를 불러올 수 있습니다.

  • editor.putString("name",name);editor.putInt("age",age); 로 키-값 쌍의 데이터를 저장할 수 있습니다. 마지막으로 editor.commit(); 을 통해 작업을 마무리합시다.

추가적으로, commit() 의 경우 동기적으로 이루어지는 작업입니다. UI 작업을 하는 메인 스레드의 작업이 중단될 수 있기 때문에 메인스레드에서 호출하기 보다는 별도의 스레드에서 호출해야하는 작업입니다.

아래 코드는 Load 버튼 눌렀을 때 실행되는 코드 입니다.

public void clickLoad(){
    SharedPreferences pref = getSharedPreferences("Data",MODE_PRIVATE);
    String name = pref.getString("name","none");
    int age = pref.getInt("age",0);
    tv.setText(name + " " + age);
}

읽어오는 작업은 크게 어렵지 않습니다. getSharedPreferences() 메소드를 이용해 어떤 방식으로 어떤 데이터를 읽어올것인지를 정하고, 각각의 키값을 이용해 데이터를 가져오면 됩니다. 단, getString() 등의 메소드를 사용할 때 두번째 파라미터로는 상응하는 키값이 없을 때 사용할 수 있는 디폴트값을 넣어주어야 합니다.


SQLite Database

앞선 데이터 저장방식은 간단한 정보들, 크기가 작은 정보들에 적합한 저장방식 입니다. 데이터베이스에 데이터를 저장하는 작업은 연락처 정보 혹은 반복적이거나 크기가 큰 데이터들을 저장하기에 이상적입니다. 데이터베이스에 데이터를 저장해보기 이전에 용어 두가지만 정리하고 넘어갑시다.

Database 와 DBMS

  • Database

데이터의 저장소인 데이터베이스 입니다. 보통 데이터는 표와 같은 형식으로 저장됩니다.

  • DBMS

DBMS 는 DataBase Management System 의 약자로, 데이터베이스를 관리하는 시스템을 의미하며, 데이터베이스와는 무관합니다. 흔히 데이터베이스를 DBMS 의 이름으로 지칭하는 경우가 많습니다. 의미는 통용되기에 큰 문제는 없으나 개념은 확실히 다르다는것은 알아둡시다.

보통 많이들 사용하는 DBMS 로는 MySQL, MsSQL, Oracle 등이 있습니다. 이러한 DBMS 는 보통 PC 환경에서 많이 사용됩니다. 그렇기 때문에 상당히 무거운 편에 속하는 소프트웨어죠. 그래서 안드로이드와 같은 모바일용에는 적합하지 않습니다. 하지만 모바일이라고 해서 데이터베이스가 필요치 않은건 또 아니죠. 그래서 조금 가볍게 하고자 나온게 SQLite 입니다. 이번 예제에서는 SQLite 의 사용법을 알아봅시다.

SQLite 는 우리가 저장할 데이터가 체계화되어있을 때, 속성값이 3개 이상 등의 경우에 이러한 Database 를 사용할 수 있습니다. 즉, 어떤 데이터를 표처럼 저장하고 싶다면 선택할 수 있는 방법입니다. 그리고 이러한 표를 Database 라고 합니다. 기본적으로 내부저장소에 저장되기 때문에 안전하게 데이터를 유지할 수 있습니다.

CRUD 작업

데이터베이스의 데이터를 관리할 때, CRUD 작업을 해야한다고들 합니다. CRUD 는 각각 Create , Read , Update , Delete 의 앞글자를 따서 만든 단어입니다. 데이터를 생성하고, 읽고, 갱신하고, 삭제한다는 의미이며 데이터 작업을 할 때의 기본 메커니즘 정도로 볼 수 있을것 같습니다. 오늘 예제에서도 이 CRUD 작업을 통해 데이터베이스를 관리해봅시다.

Create 작업

void createDb(){
    db = openOrCreateDatabase("test.db",MODE_PRIVATE,null);

    db.execSQL("CREATE TABLE IF NOT EXISTS member(num INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(20) NOT NULL, age INTEGER, email TEXT)");
}
  • db = openOrCreateDatabase("test.db",MODE_PRIVATE,null);
    dbSQLiteDatabase db; 로 생성된 SQLiteDatabase 의 객체 입니다. 그 객체에 openOrCreateDatabase() 메서드를 이용하여 어떤 이름의 데이터베이스를 만들지를 첫번째 파라미터로, 두번째 파라미터로는 데이터베이스의 저장방식을 전달합시다.

  • db.execSQL("CREATE TABLE IF NOT EXISTS member(num INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(20) NOT NULL, age INTEGER, email TEXT)");
    execSQL() 메서드의 파라미터로 쿼리문을 삽입하면, 데이터베이스를 활용할 수 있게 됩니다. 위 코드는 데이터베이스에 테이블 하나를 만드는 쿼리문 입니다. SQL 은 추후에 따로 다뤄볼 예정이기 때문에 일단은 이렇게 만드는구나 정도만 알고 넘어갑시다.

Create 작업에서는 테이블을 생성하기도 하지만 데이터를 생성하여 레코드를 생성하는 작업도 포함됩니다. 아래 코드는 레코드를 생성하는 코드입니다.

void clickInsert(){
    String name = etName.getText().toString();
    int age = Integer.parseInt(etAge.getText().toString());
    String email = etEmail.getText().toString();

    db.execSQL("INSERT INTO member (name, age, email) VALUES ('"+ name +"', '"+age+"', '"+email+"')");
}
  • String name = etName.getText().toString(); , int age = Integer.parseInt(etAge.getText().toString()); , String email = etEmail.getText().toString(); 을 이용하여 받아온 데이터를 각각의 변수에 담아 두었습니다.

  • db.execSQL("INSERT INTO member (name, age, email) VALUES ('"+ name +"', '"+age+"', '"+email+"')");
    테이블 생성작업과 마찬가지로 execSQL() 이용하여 쿼리문을 삽입 해주었습니다.

Read 작업

void clickSelectAll(){
    Cursor cursor = db.rawQuery("SELECT * FROM member",null); 
    if(cursor == null) return;
    int row = cursor.getCount();
    cursor.moveToFirst(); 

    StringBuffer buffer = new StringBuffer();
    for(int i=0;i<row;i++){
        int num = cursor.getInt(0);
        String name = cursor.getString(1);
        int age = cursor.getInt(2);
        String email = cursor.getString(3);

        buffer.append(num +" " + name +" " + age + " " + email + "\n");
        cursor.moveToNext();
    }

    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setMessage(buffer.toString());
    builder.create().show();

}

먼저, 테이블에 있는 모든 데이터를 가져오는 작업을 해봅시다.

  • Cursor cursor = db.rawQuery("SELECT * FROM member",null);
    Read 작업의 경우 execSQL() 메소드가 아닌 rawQuery() 를 이용해주어야 합니다. SELECT * FROM member 를 이용하여 member 테이블의 모든 열을 가져옵니다. 그렇게 가져온 새로운 테이블을 Cursor 객체에 담아둡시다. 두번째 파라미터에 대해서는 다음 예제에서 알아보겠습니다.

  • if(cursor == null) return;
    그렇게 가져온 cursor 객체에 아무것도 담겨있지 않다면, 메소드를 종료시킵니다. 주의할 점은 cursor 객체에 아무것도 담겨있지 않다는것의 의미입니다. SELECT 을 통해 가져온 데이터가 아무것도 없다는 의미가 아닌, SQL 구문이 무언가 잘못되었다는걸 의미합니다. 조건에 부합하는 데이터가 없다면 아무것도 없는 데이터를 가져올 뿐, null 값은 아닙니다.

  • int row = cursor.getCount();
    새로운 테이블을 가지고 있는 cursor 객체를 이용하여 행의 갯수, 즉 데이터의 갯수를 받아 row 변수에 담아둡니다.

  • cursor.moveToFirst(); 커서 객체를 테이블의 첫번째 행으로 이동시킵니다.

  • StringBuffer buffer = new StringBuffer(); 데이터를 담을 버퍼를 준비합니다. 보통은 ArrayList 와 같은 컬렉션에 담으나, 예제를 위해 버퍼를 이용했습니다.

  • 		for(int i=0;i<row;i++){
    	  	   int num = cursor.getInt(0);
        	   String name = cursor.getString(1);
               int age = cursor.getInt(2);
          	   String email = cursor.getString(3);
    
          	   buffer.append(num +" " + name +" " + age + " " + email + "\n");
          	   cursor.moveToNext();
      	    }

    for 문을 활용하여 데이터의 갯수만큼 반복문을 돌립니다. 반복문이 돌면서 커서 객체는 각 열의 데이터들을 가져와 각각의 변수에 담아둡니다. 그리고 담아둔 변수들을 버퍼객체에 append 합니다. 그렇게 한 레코드의 작업이 끝났다면, cursor.moveToNext() 를 이용하여 다음 레코드로 넘어갑니다.

    그 다음 코드는 커서 객체를 통해 받아온 데이터를 화면에 뿌려주는 코드입니다. 이번 예제에서는 다이얼로그를 통해 화면에 보여주었습니다.


이번에는 테이블의 모든 데이터가 아닌 조건에 맞는 데이터를 가져오는 예제입니다.

void clickSelectByName(){
    String name = etName.getText().toString();
    Cursor cursor = db.rawQuery("SELECT name, age FROM member WHERE name = ?",new String[]{ name });
    if(cursor == null) return;

    int row = cursor.getCount();
    cursor.moveToFirst();
    StringBuffer buffer = new StringBuffer();
    for(int i=0;i<row;i++){
        String name2 = cursor.getString(0);
        int age = cursor.getInt(1);

        buffer.append(name + " " + age + "\n");
        cursor.moveToNext();
    }

    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setMessage(buffer.toString());
    builder.create().show();
}

다른 부분은 모든 데이터를 가져오는 작업과 동일하므로, 쿼리문을 삽입할 때, WHERE 를 이용하여 조건을 삽입하는 부분만 살펴봅시다.

  • db.rawQuery("SELECT name, age FROM member WHERE name = ?",new String[]{ name });

    모든것을 의미하는 * 대신 name, age 가 삽입되었습니다. 이 뜻은 name 열과 age 열을 가져오겠다는 의미입니다. 그리고 WHERE name = ? 을 통해 조건을 지정합니다. 그 조건은 두번째 파라미터로 지정해줄 수 있습니다. 두번째 파라미터로는 문자열의 배열을 전달해주어야 하며 배열의 값이 ? 에 들어갈 값이 됩니다. 예전에 C 언어에서 했던 printf 로 서식을 지정해주는 느낌과 비슷합니다. 그리고 앞선 예제에서 null 값을 넣어준 이유는 따로 조건이 없었기 때문입니다.

Update 작업과 Delete 작업은 따로 볼 부분은 없습니다. SQL 쿼리문의 형식만 다를 뿐 이므로 한번 보고 넘어갑시다.

Update 작업

void clickUpdate(){
    String name = etName.getText().toString();
    db.execSQL("UPDATE member SET age = 30 WHERE name = ?",new String[]{ name });
}

Delete 작업

void clickDelete(){
    String name = etName.getText().toString();
    db.execSQL("DELETE FROM member WHERE name = ?",new String[]{ name });
}
profile
Developer

0개의 댓글