728x90
반응형
SMALL

1. SQLiteDatabase.

안드로이드에서 기본적으로 제공하는 데이터베이스는 SQLite입니다. SQLite는 관계형 데이터베이스입니다.

 

안드로이드에서 SQLite 데이터베이스를 사용할 때 가장 중요한 핵심 요소는 SQLiteDatabase 클래스입니다. SQLite 데이터베이스를 다루는 기능들이 SQLiteDatabase 클래스에 정의되어 있죠.

 

SQLite 데이터베이스에 데이터를 저장하고, 읽어들이는 방법에 대해 알아보도록 하겠습니다.

2. SQLiteDatabase 사용 예제

이제 앱 작성을 통해 SQLiteDatabase를 사용하여 데이터를 저장하고, 조회하는 방법에 대해 살펴보도록 하겠습니다.

예제로 작성되는 앱의 실행 화면은 아래 그림과 같이 구성됩니다.

 

데이터베이스는 아래와 같은 구조를 가집니다.

 

참고로, 보통 데이터베이스를 사용할 때는 동일한 컬럼(Column)에 따라 식별된 많은 양의 로우(Row)를 저장하는데, 본문의 예제는 하나의 로우(Row)만 저장합니다. 이는 앱의 설정 정보 등을 저장할 때 사용하는 방법인데, 설정 정보는 최종적으로 저장된 단 하나의 값만 있으면 되기 때문입니다.

2.1 MainActivity의 레이아웃 작성.

앱 작성 시, 첫 번째로 할 일은 MainActivity의 레이아웃을 작성하는 것입니다. 앞서 설계한 예제 화면 구성도에 따라, 아래와 같이 MainActivity에 표시될 레아아웃 XML 코드를 작성합니다.

[STEP-1] "activity_main.xml" - MainActivity의 레이아웃 XML 코드 작성.
    <TableLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:stretchColumns="1">

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:textSize="24sp"
                android:text="No" />

            <EditText
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="20dp"
                android:textSize="24sp"
                android:id="@+id/editTextNo"/>
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:textSize="24sp"
                android:text="Name" />

            <EditText
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="20dp"
                android:textSize="24sp"
                android:id="@+id/editTextName"/>
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:textSize="24sp"
                android:text="Phone" />

            <EditText
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="20dp"
                android:textSize="24sp"
                android:id="@+id/editTextPhone"/>
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:textSize="24sp"
                android:text="Over20" />

            <CheckBox
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="20dp"
                android:id="@+id/checkBoxOver20"/>
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_span="2">

                <Button
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:textSize="24dp"
                    android:text="Save"
                    android:id="@+id/buttonSave" />

                <Button
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:textSize="24dp"
                    android:text="CLEAR"
                    android:id="@+id/buttonClear" />
            </LinearLayout>

        </TableRow>
    </TableLayout>

2.2 SQLiteDatabase 클래스 변수 선언 및 데이터베이스 열기.

다음 단계로, SQLite 데이터베이스를 사용하는데 있어 가장 중요한 요소인, SQLiteDatabase 클래스의 객체를 초기화하는 코드를 작성합니다. SQLiteDatabase 클래스 객체는 MainActivity의 전역에서 사용되므로, 클래스 변수로 선언합니다.

그리고 최초 앱 실행 시, SQLite 데이터베이스 파일이 존재하지 않으면 새로운 파일을 만들고 데이터베이스를 열도록 SQLiteDatabase.openOrCreateDatabase() 함수를 사용합니다. 이러한 과정은 init_database() 라는 함수에 작성하고, MainActivity의 onCreate() 함수에서 호출하도록 만듭니다.

 
[STEP-2] "MainActivity.java" - SQLDatabase 클래스 변수 선언 및 초기화.
public class MainActivity extends AppCompatActivity {

    SQLiteDatabase sqliteDB ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        // ... 코드 계속
        sqliteDB = init_database() ;

        // 코드 계속  ...
    }

    // ... 코드 계속
    private SQLiteDatabase init_database() {

        SQLiteDatabase db = null ;
        // File file = getDatabasePath("contact.db") ;
        File file = new File(getFilesDir(), "contact.db") ;

        System.out.println("PATH : " + file.toString()) ;
        try {
            db = SQLiteDatabase.openOrCreateDatabase(file, null) ;
        } catch (SQLiteException e) {
            e.printStackTrace() ;
        }

        if (db == null) {
            System.out.println("DB creation failed. " + file.getAbsolutePath()) ;
        }

        return db ;
    }

    // 코드 계속  ...
}

2.3 SQLite 데이터베이스를 새로 만들 때, 테이블 생성. (CREATE TABLE)

SQLite 데이터베이스를 열어 SQLiteDatabase에 대한 참조를 확보했다면, 이제 데이터베이스 테이블에 데이터를 추가하거나 조회할 수 있습니다. 그런데 데이터베이스 파일을 새로 생성한 경우라면 테이블은 존재하지 않기 때문에, CREATE TABLE 문을 사용하여 테이블을 생성해야 합니다. CREATE TABLE 문에 IF NOT EXISTS를 더하여 테이블을 생성합니다.

    CONTACT_T 테이블 생성을 위한 SQL 문.
CREATE TABLE IF NOT EXISTS CONTACT_T (NO INTEGER NOT NULL, NAME TEXT, PHONE TEXT, OVER20 INTEGER)

아래와 같이 init_table() 이라는 함수를 새로 추가하고, onCreate()에서 init_database() 함수 다음에 호출하도록 작성합니다.

[STEP-3] "MainActivity.java" - SQLDatabase 클래스 변수 선언 및 초기화.
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        // ... 코드 계속

        sqliteDB = init_database() ;

        init_tables() ;

        // 코드 계속  ...
    }

    // ... 코드 계속
    private void init_tables() {

        if (sqliteDB != null) {
            String sqlCreateTbl = "CREATE TABLE IF NOT EXISTS CONTACT_T (" +
                    "NO "           + "INTEGER NOT NULL," +
                    "NAME "         + "TEXT," +
                    "PHONE "        + "TEXT," +
                    "OVER20 "       + "INTEGER" + ")" ;

            System.out.println(sqlCreateTbl) ;

            sqliteDB.execSQL(sqlCreateTbl) ;
        }
    }

    // 코드 계속  ...
}
 

2.4 앱 실행 시, 테이블 데이터 조회하여 표시. (SELECT)

최초 앱이 실행되면 데이터베이스에 저장된 데이터를 로딩하여 화면에 표시하는 코드를 작성합니다. 데이터 조회를 위한 SELECT문 실행 결과 데이터를 Cursor 타입으로 리턴받은 다음, moveToNext() 함수를 통해 커서로 전달된 레코드 집합(Record Set)을 탐색합니다.

    CONTACT_T 테이블에서 모든 데이터를 조회하는 SQL 문.
SELECT * FROM CONTACT_T

데이터를 조회하는 함수는 load_values() 라는 함수에 작성하고, MainActivity의 onCreate() 함수에서 init_table() 함수 바로 다음에 호출합니다.

[STEP-4] "MainActivity.java" - SELECT 문을 통해 데이터 조회 및 표시.
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        // ... 코드 계속

        sqliteDB = init_database() ;
        init_tables() ;

        load_values() ;

        // 코드 계속  ...
    }

    // ... 코드 계속
    private void load_values() {

        if (sqliteDB != null) {
            String sqlQueryTbl = "SELECT * FROM CONTACT_T" ;
            Cursor cursor = null ;

            // 쿼리 실행
            cursor = sqliteDB.rawQuery(sqlQueryTbl, null) ;

            if (cursor.moveToNext()) { // 레코드가 존재한다면,
                // no (INTEGER) 값 가져오기. 
                int no = cursor.getInt(0) ;
                EditText editTextNo = (EditText) findViewById(R.id.editTextNo) ;
                editTextNo.setText(Integer.toString(no)) ;

                // name (TEXT) 값 가져오기
                String name = cursor.getString(1) ;
                EditText editTextName = (EditText) findViewById(R.id.editTextName) ;
                editTextName.setText(name) ;

                // phone (TEXT) 값 가져오기
                String phone = cursor.getString(2) ;
                EditText editTextPhone = (EditText) findViewById(R.id.editTextPhone) ;
                editTextPhone.setText(phone) ;

                // over20 (INTEGER) 값 가져오기.
                int over20 = cursor.getInt(3) ;
                CheckBox checkBoxOver20 = (CheckBox) findViewById(R.id.checkBoxOver20) ;
                if (over20 == 0) {
                    checkBoxOver20.setChecked(false) ;
                } else {
                    checkBoxOver20.setChecked(true) ;
                }
            }
        }
    }

    // 코드 계속  ...
}

2.5 입력 데이터 저장하기. (INSERT INTO)

이제 "Add" 버튼을 누르면, 데이터를 저장하는 기능을 구현하겠습니다. 데이터를 추가할 때는 INSERT INTO문을 사용합니다. 그런데 예제에서는 테이블 내에 단 하나의 레코드만 저장하므로, 저장되어 있는 레코드를 지우고 나서 새로 추가하도록 만들겠습니다.

    CONTACT_T 테이블에서 데이터를 추가하는 SQL 문.
INSERT INTO CONTACT_T (NO, NAME, PHONE, OVER20) VALUES (1, 'ppotta', '010-8888-9999', 1)
    CONTACT_T 테이블의 모든 레코드를 삭제하는 SQL 문.
DELETE FROM CONTACT_T

데이터를 저장하는 코드는 save_values() 함수에 작성한 다음, buttonSave 버튼의 클릭 이벤트 핸들러에서 해당 함수에서 호출하도록 작성합니다.

[STEP-5] "MainActivity.java" - 데이터 저장하기.
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        // ... 코드 계속

        Button buttonSave = (Button) findViewById(R.id.buttonSave) ;
        buttonSave.setOnClickListener(new Button.OnClickListener() {
            @Override
            public void onClick(View v) {
                save_values() ;
            }
        });

        // 코드 계속  ...
    }

    // ... 코드 계속
    private void save_values() {
        if (sqliteDB != null) {
            
            // delete
            sqliteDB.execSQL("DELETE FROM CONTACT_T") ;

            EditText editTextNo = (EditText) findViewById(R.id.editTextNo) ;
            String noText = editTextNo.getText().toString() ;
            int no = 0 ;
            if (noText != null && !noText.isEmpty()) {
                no = Integer.parseInt(noText) ;
            }

            EditText editTextName = (EditText) findViewById(R.id.editTextName) ;
            String name = editTextName.getText().toString() ;

            EditText editTextPhone = (EditText) findViewById(R.id.editTextPhone) ;
            String phone = editTextPhone.getText().toString() ;

            CheckBox checkBoxOver20 = (CheckBox) findViewById(R.id.checkBoxOver20) ;
            boolean isOver20 = checkBoxOver20.isChecked() ;

            String sqlInsert = "INSERT INTO CONTACT_T " +
                    "(NO, NAME, PHONE, OVER20) VALUES (" +
                    Integer.toString(no) + "," +
                    "'" + name + "'," +
                    "'" + phone + "'," +
                    ((isOver20 == true) ? "1" : "0") + ")" ;

            System.out.println(sqlInsert) ;

            sqliteDB.execSQL(sqlInsert) ;
        }
    }

    // 코드 계속 ...
}

2.6 데이터 삭제하기. (DELETE)

마지막으로 "Clear" 버튼이 눌려지면 입력된 모든 내용을 지우고, 테이블의 데이터를 삭제합니다. DELETE 문을 사용합니다.

    CONTACT_T 테이블의 모든 레코드를 삭제하는 SQL 문.
DELETE FROM CONTACT_T

delete_values() 함수에 데이터 삭제 코드를 작성하고, buttonClear 버튼이 눌려지면 호출하도록 만듭니다.

[STEP-6] "MainActivity.java" - 데이터 삭제하기.
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        // ... 코드 계속

        Button buttonClear = (Button) findViewById(R.id.buttonClear) ;
        buttonClear.setOnClickListener(new Button.OnClickListener() {
            @Override
            public void onClick(View v) {
                delete_values() ;
            }
        });

        // 코드 계속  ...
    }

    // ... 코드 계속

    private void delete_values() {
        if (sqliteDB != null) {
            String sqlDelete = "DELETE FROM CONTACT_T" ;

            sqliteDB.execSQL(sqlDelete) ;

            EditText editTextNo = (EditText) findViewById(R.id.editTextNo) ;
            editTextNo.setText("") ;

            EditText editTextName = (EditText) findViewById(R.id.editTextName) ;
            editTextName.setText("") ;

            EditText editTextPhone = (EditText) findViewById(R.id.editTextPhone) ;
            editTextPhone.setText("") ;

            CheckBox checkBoxOver20 = (CheckBox) findViewById(R.id.checkBoxOver20) ;
            checkBoxOver20.setChecked(false) ;
        }
    }
}

3. 예제 실행 화면

예제를 작성하고 실행하면, 아래와 같은 실행 화면이 나타납니다.

 

각 필드에 값을 입력하고 "SAVE" 버튼을 누르면, 입력된 값들이 데이터베이스에 저장됩니다. 그리고 앱을 다시 실행하면, 데이터베이스에 저장된 값이 화면에 표시되는 것을 확인할 수 있습니다.

 

"CLEAR" 버튼을 누르면 데이터베이스에 저장된 값이 지워지고, 화면에 표시된 내용이 초기화되는 것을 확인할 수 있습니다.

 

4. 참고.

728x90
반응형
LIST
728x90
반응형
SMALL

1. SQLiteDatabase 사용 예제에 대한 문제와 개선

SQLiteDatabase.openOrCreateDatabase() 함수를 호출하여 데이터베이스를 열고, SQLiteDatabase 클래스 객체를 확보한 다음, 데이터베이스를 다루는 함수를 사용하여 데이터를 추가하거나, 수정, 삭제 또는 조회하는 예제를 작성하였습니다.

작성한 예제가, SQLiteDatabase에 대한 사용법을 보여주고 있고, 앱의 동작도 문제가 없지만, 코드 작성에 대하여 몇 가지 개선해야 할 점이 있습니다.

1.1 SQL Helper(SQLiteOpenHelper 클래스) 사용.

예제에서 데이터베이스 파일을 열 때 SQLiteDatabase.openOrCreateDatabase() 함수를 직접 호출했는데, 안드로이드에서는 이 방법보다 SQLiteOpenHelper 클래스를 사용하기를 권장하고 있습니다.

 

SQLiteOpenHelper를 사용하면 SQLiteDatabase.openOrCreateDatabase() 함수를 사용하여 데이터베이스를 열 때 파일의 경로까지 직접 지정해야 하는 번거로움을 해소할 수 있습니다. 그리고 앱의 기능 수정으로 인해 데이터베이스 테이블의 구조가 바뀌어야 하는 상황에 대해서도 적절히 대처할 수 있는 기반을 제공함으로써 데이터베이스 관리를 용이하게 만들어줍니다.

 

데이터베이스를 열 때는 SQLiteDatabase.openOrCreateDatabase()를 직접 호출하지 말고, SQLiteOpenHelper를 사용하는 것이 좋습니다. 일단 SQLiteOpenHelper 객체를 생성하고 나면, SQLiteOpenHelper의 getWritableDatabase() 또는 getReadableDatabase() 함수를 통해 SQLiteDatabase 객체를 참조할 수 있습니다.

1.2 계약 클래스 (Contract Class) 사용.

예제에서는 SQL 문장을 만들 때, 테이블 이름(CONTACT_T)과 필드 이름(NO, NAME, ...)을 매번 직접 지정하였습니다.

    private void init_tables() {
        // 코드 계속 ...
        String sqlCreateTbl = "CREATE TABLE IF NOT EXISTS CONTACT_T (" +
                    "NO "           + "INTEGER NOT NULL," +
                    "NAME "         + "TEXT," +
                    "PHONE "        + "TEXT," +
                    "OVER20 "       + "INTEGER" + ")" ;
        // ... 코드 계속
    }

    private void load_values() {
        // 코드 계속 ...
        String sqlQueryTbl = "SELECT * FROM CONTACT_T" ;
        // ... 코드 계속
    }

    private void save_values() {
        // 코드 계속 ...
        String sqlInsert = "INSERT INTO CONTACT_T " +
                    "(NO, NAME, PHONE, OVER20) VALUES (" +
                    Integer.toString(no) + "," +
                    "'" + name + "'," +
                    "'" + phone + "'," +
                    ((isOver20 == true) ? "1" : "0") + ")" ;
        // ... 코드 계속
    }

기능 수정 또는 확장으로 인해 데이터베이스 구조가 변경되어야 한다면 어떻게 될까요? 그러한 구조 변경으로 인해 테이블 이름 또는 필드 이름이 수정되어야 된다면? 모든 테이블, 필드 이름을 찾아서 수정된 이름으로 변경해야합니다.

 

테이블이나 필드 이름이 사용되는 곳이 얼마 없으니 상관없지 않을까... 라고 생각할 수도 있습니다. 하지만 테이블과 필드의 갯수가 많아지고 SQL 문장을 조합하는 곳이 많아지면, 이름 수정 작업은 큰 부담으로 다가올 수 밖에 없습니다.

 

이런 문제를 해결할 수 있는 방법은 계약 클래스(Contract Class)를 사용하는 것입니다.

 

계약 클래스(Contract Class)는 프로그램 개발 과정에서 참조되는 여러 상수들을 정의한 클래스를 말합니다. 이 의미를 데이터베이스에서 적용해보자면, 데이터베이스의 계약 클래스(Contract Class)란, 테이블 이름, 열(Column) 이름, 기능 별 SQL 문장들에 대한 상수 정의를 포함한 클래스를 의미합니다. "계약(Contract)"이라는 단어가 "어떤 행위를 함에 있어 관련된 사람들이 지켜야 할 의무 또는 약속을 문서로 남긴 것"을 의미하듯, 계약 클래스(Contract Class)는 "데이터베이스를 사용함에 있어 개발자가 사용해야 할 여러 정보를 상수로 정의해둔 것"이라고 정리할 수 있습니다.

 

계약 클래스(Contract Class)를 사용함으로써 얻을 수 있는 장점은 데이터베이스와 관련된 정보를 한 곳에서 관리할 수 있다는 것과, 데이터베이스 구조 또는 이름이 변경될 때의 수정 작업을 최소화할 수 있다는 것입니다.

 

계약 클래스(Contract Class)를 만들 때는 앱의 패키지 루트에 생성하는 것이 좋습니다. 앱 소스의 어디서든 바로 참조 가능하도록 만드는 것이 좋기 때문입니다. 그리고 계약 클래스(Contract Class)는 인스턴스를 만들 필요가 없기 때문에 생성자의 접근 제한자(Access Modifier)를 private으로 선언합니다.

 

계약 클래스(Contract Class)에 대한 구체적인 사용 방법은 아래 예제의 ContactDBCtrct 클래스를 참고하시기 바랍니다.

 

2. 개선된 SQLite 데이터베이스 사용 예제

예제의 화면 레이아웃은 이전 예제에서 사용한 레이아웃을 수정없이 그대로 사용하겠습니다.

 

2.1 MainActivity의 레이아웃 작성.

MainActivity의 레이아웃은 이전 예제에서 사용한 코드를 그대로 적용합니다.

[STEP-1] "activity_main.xml" - MainActivity의 레이아웃 XML 코드 작성.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:id="@+id/content_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.ppottasoft.databaseadvancedexample.MainActivity"
    tools:showIn="@layout/activity_main">

    <TableLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:stretchColumns="1">

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:textSize="24sp"
                android:text="No" />

            <EditText
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="20dp"
                android:textSize="24sp"
                android:id="@+id/editTextNo"/>
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:textSize="24sp"
                android:text="Name" />

            <EditText
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="20dp"
                android:textSize="24sp"
                android:id="@+id/editTextName"/>
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:textSize="24sp"
                android:text="Phone" />

            <EditText
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="20dp"
                android:textSize="24sp"
                android:id="@+id/editTextPhone"/>
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:textSize="24sp"
                android:text="Over20" />

            <CheckBox
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="20dp"
                android:id="@+id/checkBoxOver20"/>
        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_span="2">

                <Button
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:textSize="24dp"
                    android:text="Save"
                    android:id="@+id/buttonSave" />

                <Button
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:textSize="24dp"
                    android:text="CLEAR"
                    android:id="@+id/buttonClear" />
            </LinearLayout>

        </TableRow>
    </TableLayout>
</RelativeLayout>

2.2 계약 클래스 (Contract Class) 추가.

코드 개선의 첫 번째 과정으로, 데이터베이스와 관련된 상수를 포함하는 계약 클래스(Contract Class)를 추가합니다. 계약 클래스(Contract Class)에 어떤 정보를 포함시킬 것인지는 개발자의 결정에 따릅니다. 단순히 데이터베이스 이름, 테이블 이름, 열(Column) 이름 정도의 상수만 포함시킬 수 있고, 더 나아가 단순 조합 SQL 문 또는 기능 별 컬럼과 파라미터 조합을 리턴하는 클래스 정적 메소드까지 선언할 수도 있습니다.

 

예제에서 사용하는 계약 클래스는 테이블 이름, 열(Column) 이름, 단순 조합 SQL 문 정도만 포함합니다.

 
[STEP-2] "ContactDBCtrct.java" - 데이터베이스 Contact를 위한 계약 클래스.
public class ContactDBCtrct {

    private ContactDBCtrct() {} ;

    public static final String TBL_CONTACT = "CONTACT_T" ;
    public static final String COL_NO = "NO" ;
    public static final String COL_NAME = "NAME" ;
    public static final String COL_PHONE = "PHONE" ;
    public static final String COL_OVER20 = "OVER20" ;

    // CREATE TABLE IF NOT EXISTS CONTACT_T (NO INTEGER NOT NULL, NAME TEXT, PHONE TEXT, OVER20 INTEGER)
    public static final String SQL_CREATE_TBL = "CREATE TABLE IF NOT EXISTS " + TBL_CONTACT + " " +
            "(" +
                COL_NO +        " INTEGER NOT NULL" +   ", " +
                COL_NAME +      " TEXT"             +   ", " +
                COL_PHONE +     " TEXT"             +   ", " +
                COL_OVER20 +    " INTEGER"          +
            ")" ;

    // DROP TABLE IF EXISTS CONTACT_T
    public static final String SQL_DROP_TBL = "DROP TABLE IF EXISTS " + TBL_CONTACT ;

    // SELECT * FROM CONTACT_T
    public static final String SQL_SELECT = "SELECT * FROM " + TBL_CONTACT ;

    // INSERT OR REPLACE INTO CONTACT_T (NO, NAME, PHONE, OVER20) VALUES (x, x, x, x)
    public static final String SQL_INSERT = "INSERT OR REPLACE INTO " + TBL_CONTACT + " " +
            "(" + COL_NO + ", " + COL_NAME + ", " + COL_PHONE + ", " + COL_OVER20 + ") VALUES " ;

    // DELETE FROM CONTACT_T
    public static final String SQL_DELETE = "DELETE FROM " + TBL_CONTACT ;
}

2.3 SQLiteOpenHelper 상속 및 구현.

코드 개선의 두 번째 과정으로, SQLiteOpenHelper를 상속하는 클래스를 만듭니다.

[STEP-3] "ContactDBHelper.java" - SQLiteOpenHelper를 상속한 Helper 클래스 정의
public class ContactDBHelper extends SQLiteOpenHelper {

    public static final int DB_VERSION = 1 ;
    public static final String DBFILE_CONTACT = "contact.db" ;

    public ContactDBHelper(Context context) {
        super(context, DBFILE_CONTACT, null, DB_VERSION);
    }
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(ContactDBCtrct.SQL_CREATE_TBL) ;
    }
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // db.execSQL(ContactDBCtrct.SQL_DROP_TBL) ;
        onCreate(db) ;
    }
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // onUpgrade(db, oldVersion, newVersion);
    }
}

2.4 데이터베이스 Helper 클래스 변수 선언 및 초기화.

SQLiteOpenHelper클래스를 상속한 ContactDBHelper를 사용하기 위해 MainActivity에 클래스 변수로 선언하고, 초기화 과정을 수행합니다. 초기화 과정은 매우 간단합니다. ContactDBHelper 클래스의 인스턴스를 만드는 것만으로 수행되니까요.

[STEP-4] "MainActivity.java" - ContactDBHelper 변수 선언 및 초기화.
public class MainActivity extends AppCompatActivity {

    ContactDBHelper dbHelper = null ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // 코드 계속 ...

        // init sqlite db helper.
        init_tables() ;

        // ... 코드 계속
    }

    private void init_tables() {
        dbHelper = new ContactDBHelper(this) ;
    }

    // ... 코드 계속
}

2.5 앱 실행 시, 테이블 데이터 조회하여 표시. (SELECT)

이제 새로 추가된 ContactDBHelper 클래스 객체를 사용하는 데이터 조회 기능을 작성하겠습니다.                           예제에서는 SQLiteDatabase.openOrCreateDatabase() 함수를 통해 확보한 SQLiteDatabase 객체를 직접 사용했지만, 여기서는 SQLiteOpenHelper 클래스에서 제공하는 getReadableDatabase()함수를 통해 SQLiteDatabase 객체를 가져옵니다.

 

그리고 데이터 조회를 위한 SELECT 문을 직접 만들지 않고, 계약 클래스에 미리 만들어둔 ContactDBCtrct.SQL_SELECT 를 사용합니다.

 
[STEP-5] "MainActivity.java" - SELECT 문을 통해 데이터 조회 및 표시.
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        // ... 코드 계속

        // init sqlite db helper.
        init_tables() ;

        // load values from db.
        load_values() ;

        // 코드 계속  ...
    }

    private void load_values() {

        SQLiteDatabase db = dbHelper.getReadableDatabase() ;
        Cursor cursor = db.rawQuery(ContactDBCtrct.SQL_SELECT, null) ;

        if (cursor.moveToFirst()) {
            // no (INTEGER) 값 가져오기.
            int no = cursor.getInt(0) ;
            EditText editTextNo = (EditText) findViewById(R.id.editTextNo) ;
            editTextNo.setText(Integer.toString(no)) ;

            // name (TEXT) 값 가져오기
            String name = cursor.getString(1) ;
            EditText editTextName = (EditText) findViewById(R.id.editTextName) ;
            editTextName.setText(name) ;

            // phone (TEXT) 값 가져오기
            String phone = cursor.getString(2) ;
            EditText editTextPhone = (EditText) findViewById(R.id.editTextPhone) ;
            editTextPhone.setText(phone) ;

            // over20 (INTEGER) 값 가져오기.
            int over20 = cursor.getInt(3) ;
            CheckBox checkBoxOver20 = (CheckBox) findViewById(R.id.checkBoxOver20) ;
            if (over20 == 0) {
                checkBoxOver20.setChecked(false) ;
            } else {
                checkBoxOver20.setChecked(true) ;
            }
        }
    }

    // 코드 계속  ...
}

2.6 입력 데이터 저장하기. (INSERT INTO)

데이터 조회 기능과 마찬가지로, 데이터베이스 Helper 클래스로부터 SQLiteDatabase의 객체를 가져온 다음, INSERT 문을 실행합니다. INSERT 문이 실행되면 데이터베이스의 내용이 변경되기 때문에, SQLiteOpenHelper.getWritableDatabase() 함수를 통해 SQLiteDatabase 객체를 가져와야 합니다.

[STEP-6] "MainActivity.java" - 데이터 저장하기.
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        // ... 코드 계속

        Button buttonSave = (Button) findViewById(R.id.buttonSave) ;
        buttonSave.setOnClickListener(new Button.OnClickListener() {
            @Override
            public void onClick(View v) {
                save_values() ;
            }
        });

        // 코드 계속  ...
    }

    // ... 코드 계속

    private void save_values() {
        SQLiteDatabase db = dbHelper.getWritableDatabase() ;

        db.execSQL(ContactDBCtrct.SQL_DELETE) ;

        EditText editTextNo = (EditText) findViewById(R.id.editTextNo) ;
        int no = Integer.parseInt(editTextNo.getText().toString()) ;

        EditText editTextName = (EditText) findViewById(R.id.editTextName) ;
        String name = editTextName.getText().toString() ;

        EditText editTextPhone = (EditText) findViewById(R.id.editTextPhone) ;
        String phone = editTextPhone.getText().toString() ;

        CheckBox checkBoxOver20 = (CheckBox) findViewById(R.id.checkBoxOver20) ;
        boolean isOver20 = checkBoxOver20.isChecked() ;

        String sqlInsert = ContactDBCtrct.SQL_INSERT + 
                " (" +
                Integer.toString(no) + ", " +
                "'" + name + "', " +
                "'" + phone + "', " +
                ((isOver20 == true) ? "1" : "0") +
                ")" ;

        db.execSQL(sqlInsert) ;
    }

    // 코드 계속 ...
}

2.7 데이터 삭제하기. (DELETE)

삭제하는 코드 또한 개선된 방법을 적용합니다.

[STEP-7] "MainActivity.java" - 데이터 삭제하기.
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        // ... 코드 계속

        Button buttonClear = (Button) findViewById(R.id.buttonClear) ;
        buttonClear.setOnClickListener(new Button.OnClickListener() {
            @Override
            public void onClick(View v) {
                delete_values() ;
            }
        });

        // 코드 계속  ...
    }

    // ... 코드 계속

    private void delete_values() {
        SQLiteDatabase db = dbHelper.getWritableDatabase() ;

        db.execSQL(ContactDBCtrct.SQL_DELETE) ;

        EditText editTextNo = (EditText) findViewById(R.id.editTextNo) ;
        editTextNo.setText("") ;

        EditText editTextName = (EditText) findViewById(R.id.editTextName) ;
        editTextName.setText("") ;

        EditText editTextPhone = (EditText) findViewById(R.id.editTextPhone) ;
        editTextPhone.setText("") ;

        CheckBox checkBoxOver20 = (CheckBox) findViewById(R.id.checkBoxOver20) ;
        checkBoxOver20.setChecked(false) ;
    }
}

3. 예제 실행 화면

예제의 실행 화면은 이전 예제와 동일합니다.

 

각 필드에 값을 입력하고 "SAVE" 버튼을 누르면, 입력된 값들이 데이터베이스에 저장됩니다. 그리고 앱을 다시 실행하면, 데이터베이스에 저장된 값이 화면에 표시되는 것을 확인할 수 있습니다.

 

4. 참고.

728x90
반응형
LIST
728x90
반응형
SMALL

1. 관계형 데이터베이스(Relational Database)

안드로이드에서 데이터베이스를 사용하는 구체적인 방법을 설명하기에 앞서, 데이터베이스 프로그래밍을 하기 위해서 미리 알아두면 좋을 데이터베이스 이론에 대해 설명드리겠습니다.

 

<데이터베이스 이론>                                                                                                                           안드로이드에서 기본적으로 제공되는 데이터베이스인 SQLite가 관계형 데이터베이스 구조를 따르고, 또한 표준으로 정의된 SQL 기능의 대부분을 지원하기 때문에, 안드로이드에서 SQLite를 사용하기 위해 데이터베이스 이론을 공부한다는 것은 상당한 도움이 되는 과정이라 할 수 있습니다.

<안드로이드에서 제공되는 SQLite 관련 클래스에 대한 구조 및 API 함수들의 사용법>                                          대부분의 내용이 데이터베이스 처리를 위한 SQL 문과 그 SQL을 실행하는 함수에 대한 설명, 그리고 함수의 실행 결과를 확인하는 방법들로 구성될텐데요, 여기서 설명하는 내용 정도만 이해하고 있어도 실질적인 구현 과정에서 큰 어려움없이 SQLite 를 사용할 수 있을거라 생각합니다.

 

<SQLite를 다루는 클래스에 대한 구조와 API 함수들의 사용법에 대해 설명>

2. 안드로이드에서 SQLite 사용하기.

안드로이드에서 기본적으로 제공되는 데이터베이스는 SQLite입니다. SQLite는 비교적 작은 규모의 안드로이드 앱에서 사용하기 적합한 데이터베이스로써, SQLite에서 제공하는 몇 가지 API 함수를 호출하는 것만으로 데이터베이스 기능을 사용할 수 있는 특징이 있습니다.

 

안드로이드의 SQLite 관련 클래스 및 API 함수는 "android.database.sqlite" 패키지에 들어 있으며, 그 중 가장 중요한 클래스는 SQLiteDatabase 클래스입니다.

 

아래 그림은 앞으로 설명할 SQLite 데이터베이스를 사용하는 과정을 요약한 것입니다.

 

2.1 SQLiteDatabase 클래스

SQLite 데이터베이스에 데이터를 추가(INSERT)하거나, 삭제(DELETE), 수정(UPDATE) 또는 조회(SELECT)를 하기 위해서는 SQLiteDatabase 클래스를 사용해야 합니다. 그리고 데이터가 저장될 테이블 생성 및 삭제, 수정 등의 기능 또한 SQLiteDatabase 클래스에서 제공되죠. (https://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html)

 

즉, SQLiteDatabase 클래스는 하나의 SQLite 데이터베이스를 다루기 위한 핵심 역할을 수행하는 클래스입니다. 그러므로 SQLite 데이터베이스 작업을 수행하기 전 반드시 SQLiteDatabase 클래스 객체의 참조를 획득해야 합니다.

 

일단 객체에 대한 참조를 획득하고 나면, SQLiteDatabase 클래스에 정의된 함수를 통해 데이터베이스 기능을 사용할 수 있습니다.

 

SQLiteDatabase 객체의 참조를 획득하는 것은 SQLite 데이터베이스 파일을 열거나, 새로운 파일을 생성함으로써 획득할 수 있습니다.

2.2 SQLite 데이터베이스 열기. (SQLiteDatabase 객체 참조 획득)

SQLite 데이터베이스를 사용하기 위해서는 가장 먼저 데이터베이스 파일을 열거나 생성해야 합니다.  SQLiteDatabase 클래스에 정의된 몇 가지 static 함수를 통해 수행될 수 있습니다.

리턴 타입메소드 이름

SQLiteDatabase openDatabase(String path, SQLiteDatabase.CursorFactory factory, int flags)
SQLiteDatabase openDatabase(String path, SQLiteDatabase.CursorFactory factory, int flags, DatabaseErrorHandler errorHandler)
SQLiteDatabase openOrCreateDatabase(File file, SQLiteDatabase.CursorFactory factory)
SQLiteDatabase openOrCreateDatabase(String path, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler)
SQLiteDatabase openOrCreateDatabase(String path, SQLiteDatabase.CursorFactory factory)

표에 나와 있는 static 함수의 이름을 통해 알 수 있듯이, 데이터베이스를 여는 과정에서 만날 수 있는 상황은 크게 두 가지로 나뉠 수 있습니다. "이미 데이터베이스 파일이 존재하는 경우(openDatabase() 함수 사용)"와 "데이터베이스 파일이 없을 수도 있는 경우(openOrCreateDatabase() 함수 사용)" 입니다. 주로 openOrCreateDatabase() 함수를 사용하여, 데이터베이스 파일 열기를 시도한 다음 만약 파일이 존재하지 않는다면 새로운 데이터베이스 파일을 생성하는 방법을 사용합니다.

 

아래의 코드는 openOrCreateDatabase() 함수를 사용하여 "sample.db" 파일을 여는 예제입니다. "sample.db" 파일이 존재하지 않는다면, 새로 만들게 됩니다.

 
[STEP-1] SQLiteDatabase - 데이터베이스 열기. (openOrCreateDatabase() 함수 호출.)
    SQLiteDatabase sqliteDB = null ;

    try {
        sqliteDB = SQLiteDatabase.openOrCreateDatabase("sample.db", null) ;
    } catch (SQLiteException e) {
        e.printStackTrace() ;
    }

2.3 SQLite 데이터베이스에 테이블 생성하기. (CREATE TABLE)

SQLite 데이터베이스 파일을 열어 SQLiteDatabase 객체의 참조를 확보했다면, 이제 객체의 참조를 통해 데이터베이스에 데이터를 추가하거나, 삭제 또는 조회 등의 작업을 수행할 수 있습니다. 하지만 데이터베이스 파일을 열었다고 해서 무턱대고 데이터를 추가할 순 없습니다.

앞서 관계형 데이터베이스에 대해 설명할 때, 데이터베이스에 데이터를 저장하기 위해 데이터를 구조화하는 과정에 대해 간단히 언급하였습니다. 이 때 데이터 구조화의 결과로써, 데이터의 속성과 그 값의 관계를 나타내는 테이블(Table)이 만들어지는 것을 설명했습니다.

 

SQLite 데이터베이스 파일을 열었다면, 다음 해야 할 일은 데이터베이스 내에 테이블(Table)을 생성하는 것입니다. SQLite 데이터베이스에 테이블을 만드는 방법은 테이블 생성을 위한 SQL 문자열을 SQLiteDatabase 클래스의 execSQL() 함수를 통해 전달하는 것입니다.

 

아래 예제 코드는 정수형(INTEGER) 데이터를 저장하기 위한 "NO" 필드와 문자열(TEXT) 데이터를 저장하기 위한 "NAME" 필드를 가지는 "ORDER_T"라는 테이블을 생성하는 코드입니다.

[STEP-2] SQLiteDatabase - 테이블 만들기. ("CREATE TABLE ... " 문을 execSQL()로 실행.)
    String sqlCreateTbl = "CREATE TABLE ORDER_T (NO INTEGER, NAME TEXT)" ;

    sqliteDB.execSQL(sqlCreateTbl) ;

그런데 "CREATE TABLE" SQL 문은 데이터베이스 파일이 생성되고나서 최초에 한번만 실행할 수 있습니다. 만약 생성하고자 하는 테이블과 같은 이름의 테이블이 이미 존재하는 상황에서 "CREATE TABLE" 명령을 실행하면, 다음과 같이 예외 상황이 발생합니다.

Caused by: android.database.sqlite.SQLiteException: table ORDER_T already exists (code 1): , while compiling: CREATE TABLE ORDER_T (NO INTEGER, NAME TEXT)

이런 경우, 테이블 중복 생성으로 인한 예외 상황(Exception)이 발생하는 것을 막기 위해서는 테이블이 존재하지 않는 경우에만 테이블을 새로 만들도록 만들어야 합니다. 이를 위해서는 "CREATE TABLE" 문에 "IF NOT EXISTS" 옵션을 추가하여 실행하면 됩니다. 예외 상황이 발생하는 문제를 해결한 코드는 아래와 같습니다.

[STEP-2.1] SQLiteDatabase - 테이블이 없는 경우 새로 만들기. ("CREATE TABLE IF NOT EXISTS ... " 문을 execSQL()로 실행.)
    String sqlCreateTbl = "CREATE TABLE IF NOT EXISTS ORDER_T (NO INTEGER, NAME TEXT)" ;

    sqliteDB.execSQL(sqlCreateTbl) ;

2.4 테이블에 데이터 추가, 수정, 삭제하기.

2.4.1 테이블에 데이터 추가하기. (INSERT)

SQLite 데이터베이스를 열고 테이블을 생성했다면, 이제 생성된 테이블에 데이터를 추가할 수 있습니다. 테이블에 데이터를 추가할 때는 "INSERT" 문을 사용합니다.

 

아래 예제 코드는 앞서 만든 "ORDER_T" 테이블의 "NO", "NAME" 필드에 각각 1과 "ppotta" 값을 추가하는 코드입니다. 앞서 테이블 생성 때와 마찬가지로 SQLiteDatabase 클래스의 execSQL() 함수를 사용합니다.

 
[STEP-3] SQLiteDatabase - 데이터 추가. ("INSERT INTO ... " 문을 execSQL()로 실행.)
    String sqlInsert = "INSERT INTO ORDER_T (NO, NAME) VALUES (1, 'ppotta')" ;

    sqliteDB.execSQL(sqlInsert) ;

"INSERT" 문을 실행하면 테이블에 새로운 데이터를 추가합니다. 이 때 새로 추가되는 값은 테이블 내 동일한 값을 가진 로우(Row)의 존재 여부와 관계없이, 새로운 로우(Row)로 추가됩니다.

 

그런데 어떤 상황에서는 새로운 값이 추가되는 것 대신, 기존에 저장된 로우(Row)의 값을 수정하고 싶을 때도 있을 것입니다. 물론 뒤에서 살펴볼 "UPDATE" 명령을 통해 데이터를 수정하는 것이 일반적이긴 하지만, "INSERT" 명령을 실행할 때 미리 조건에 맞는 데이터가 있으면, 새로운 로우(Row)를 추가하지 않고 이미 들어있던 로우(Row)의 값을 수정하도록 만들 수 있습니다. 이를 위해 "INSERT OR REPLACE" 문을 사용합니다.

[STEP-3.1] SQLiteDatabase - 데이터 추가. 이미 존재하면 수정. ("INSERT OR REPLACE INTO ... " 문을 execSQL()로 실행.)
    String sqlInsert = "INSERT OR REPLACE INTO ORDER_T (NO, NAME) VALUES (1, 'ppotta')" ;

    sqliteDB.execSQL(sqlInsert) ;

2.4.2 테이블 데이터 수정하기. (UPDATE)

테이블에 데이터가 추가되어 있다면, "UPDATE" 문을 사용하여 데이터의 내용을 수정할 수 있습니다.

 

아래 예제 코드는 ORDER_T 테이블의 "NO"와 "NAME" 필드 값을 각각 2, "ppotta2"로 수정하는 코드입니다.

[STEP-4] SQLiteDatabase - 데이터 수정. ("UPDATE ... " 문을 execSQL()로 실행.)
    String sqlUpdate = "UPDATE ORDER_T SET NO=2, NAME='ppotta2'" ;

    sqliteDB.execSQL(sqlUpdate) ;

그런데 위의 코드를 수행하면 테이블 내의 모든 행의 값이 수정됩니다. 이는 "UPDATE" 문이 실행될 데이터에 대한 조건이 지정되지 않았기 때문입니다. 만약 모든 로우(Row)가 아닌 특정 로우(Row)의 값만 수정하고자 한다면, 아래와 같이 "UPDATE" 문에 "WHERE"를 사용하여 조건을 추가하면 됩니다.

 

아래는 "WHERE"를 사용하여 "NO" 필드 값이 1인 로우(Row)에 대해서만 값을 수정하도록 만드는 코드입니다.

[STEP-4.1] SQLiteDatabase - 조건에 해당하는 데이터 수정. ("UPDATE ... WHERE ... " 문을 execSQL()로 실행.)
    String sqlUpdate = "UPDATE ORDER_T SET NO=2, NAME='ppotta2' WHERE NO=1" ;

    sqliteDB.execSQL(sqlUpdate) ;

2.4.3 테이블 데이터 삭제하기. (DELETE)

테이블에 저장되어 있는 데이터를 삭제하려면 "DELETE" 문을 사용합니다.

 

아래 예제 코드는 "ORDER_T" 테이블의 모든 데이터를 삭제하는 코드입니다.

[STEP-5] SQLiteDatabase - 데이터 삭제. ("DELETE ... " 문을 execSQL()로 실행.)
    String sqlDelete = "DELETE FROM ORDER_T" ;

    sqliteDB.execSQL(sqlDelete) ;

"ORDER_T" 테이블의 데이터 중에서 특정 로우(Row)만 삭제하길 원한다면 "WHERE"를 추가하여 조건을 지정할 수 있습니다.

 

아래 코드는 "NO"의 값이 2인 모든 데이터를 삭제하는 예제입니다.

[STEP-5.1] SQLiteDatabase - 조건에 맞는 데이터 삭제. ("DELETE ... WHERE ... " 문을 execSQL()로 실행.)
    String sqlDelete = "DELETE FROM ORDER_T WHERE NO=2" ;

    sqliteDB.execSQL(sqlDelete) ;

 

 

2.5 테이블 데이터 조회하기.

테이블에 저장된 데이터를 조회하려면 "SELECT" 문을 사용합니다. 하지만 앞에서 살펴 본 데이터 추가, 수정, 삭제를 위한 SQL 문장을 실행할 때와는 다르게 추가적으로 알아두어야 할 요소가 두 가지 있습니다. 바로 쿼리(Query)와 커서(Cursor) 입니다.

2.5.1 쿼리(Query)

쿼리(Query)라는 단어를 우리 말로 표현할 때 주로 "질의"라는 용어를 사용합니다. "질의"라는 단어의 사전적 의미는, "의심나거나 모르는 점을 묻는 것"을 말합니다. 자신이 모르는 사실을 알기 위해, 누군가에게 질문하여 정보를 요청하는 것이 바로 "쿼리(Query)"라는 단어의 의미인 것입니다.

 

이제 관점을 데이터베이스로 옮겨보도록 하겠습니다. 데이터베이스에 저장된 데이터는, 그 정보를 획득하기 전까지는 사용자가 모르는(가지고 있지 않은) 정보입니다. 그래서 만약 그 정보를 얻기 위해서 데이터베이스 시스템에 정보를 요청한다면, 우리는 그것을 "데이터베이스에 쿼리(Query)"한다고 말할 수 있는 것입니다.

 

정리하자면, 쿼리(Query)란, 원하는 데이터를 얻기 위해 데이터베이스에 정보를 요청(Request)하는 것을 말하며, SQLite 데이터베이스에서 그 요청(Request)은 "SELECT" 문을 사용하여 작성할 수 있습니다.

 

그런데 앞서 살펴본 데이터 추가(INSERT), 수정(UPDATE), 삭제(DELETE)는 데이터베이스로 전달되는 단-방향 명령인데 반해, 조회(SELECT)를 위한 쿼리(Query)는 데이터베이스로부터 결과 데이터 전달이 필요한 양-방향 명령입니다. 그러므로 아무런 값도 리턴하지 않는 execSQL() 함수 대신, "SELECT" 문의 조건에 따라 선택된 레코드 집합(RecordSet)을 리턴하는 query() 함수 또는 rawQuery() 함수를 사용해야 합니다.

 

리턴 타입메소드

Cursor query(boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)
Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)
Cursor query(boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit, CancellationSignal cancellationSignal)
Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)
Cursor queryWithFactory(SQLiteDatabase.CursorFactory cursorFactory, boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit, CancellationSignal cancellationSignal)
Cursor queryWithFactory(SQLiteDatabase.CursorFactory cursorFactory, boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)
Cursor rawQuery(String sql, String[] selectionArgs, CancellationSignal cancellationSignal)
Cursor rawQuery(String sql, String[] selectionArgs)
Cursor rawQueryWithFactory(SQLiteDatabase.CursorFactory cursorFactory, String sql, String[] selectionArgs, String editTable, CancellationSignal cancellationSignal)
Cursor rawQueryWithFactory(SQLiteDatabase.CursorFactory cursorFactory, String sql, String[] selectionArgs, String editTable)

많은 종류의 쿼리(Query) 함수가 제공되긴 하지만 많은 경우에 "SELECT" 문 전체를 한번에 전달할 수 있는 rawQuery() 함수가 주로 사용됩니다.

 

또한 모든 쿼리(Query) 함수가 리턴하는 레코드 집합(RecordSet)은 Cursor 인터페이스 타입으로 전달됩니다.

 

참고로 레코드 집합(RecordSet)이라는 용어는 쿼리(Query) 결과에 포함된 레코드(Record)의 묶음(Set)을 말하는 것입니다. 보통 데이터를 쿼리(Query)한 결과는 하나 또는 하나 이상의 레코드(Record)를 포함하고 있는데, 이렇게 리턴된 결과 레코드들의 집합을 레코드 집합(RecordSet)이라고 지칭합니다.

 

또한 레코드 집합(RecordSet)을 다른 용어로 로우 집합(RowSet)이라고 부르기도 합니다. 이전 글에서 관계형 데이터베이스 용어에 대해 설명할 때 "로우(Row)=레코드(Record)"라는 것을 설명하였으므로 그 연관성을 쉽게 이해할 수 있으리라 생각합니다.

2.5.2 커서(Cursor)

일반적인 컴퓨팅 환경에서 커서(Cursor)란, 화면에 표시된 내용에서 사용자가 내용 입력 또는 확인을 위해, 사용자가 현재 주시하고 있는 위치에 대한 표시를 말합니다. 키보드 커서 또는 마우스 커서 등이 대표적입니다.
그리고 데이터베이스에서도 그 의미는 크게 다르지 않습니다. 데이터베이스의 쿼리 결과로 리턴된 데이터에서 현재 그 내용을 확인하고 있는 위치를 나타내는 정보를, 커서(Cursor)라는 용어를 사용하여 지칭합니다.

 

데이터베이스에 저장된 데이터를 쿼리하면 그 결과 데이터는, 한 개의 레코드만 가지거나, 또는 여러 개의 레코드가 포함된 레코드 집합(RecordSet)입니다. 이 때 레코드 집합(RecordSet)에 들어 있는 개별 레코드에 접근하여 그 값을 확인할 수 있는 기능을 제공해주는 것이 바로 커서(Cursor)가 하는 역할인 것입니다. 그래서 SQLiteDatabase가 제공하는 모든 쿼리(Query) 관련 함수들은 Cursor 타입을 리턴하도록 되어 있습니다.

 

안드로이드에서 데이터베이스 커서(Cursor) 기능은 Cursor(android.database.Cursor) 인터페이스에 정의되어 있습니다. (https://developer.android.com/reference/android/database/Cursor.html)

 

Cursor 인터페이스에는 많은 수의 함수가 정의되어 있습니다. 하지만 대부분의 경우, 몇 개의 함수 사용만으로 쿼리 결과로 리턴된 데이터를 처리할 수 있습니다.

아래 예제 코드는 Cursor 인터페이스를 사용하여 쿼리 결과 데이터를 탐색하는 과정을 나타낸 것입니다.

    Cursor cursor = sqliteDB.rawQuery("SELECT ...", null) ;

    while (cursor.moveToNext()) {
        // 첫 번째 컬럼(Column)이 INTEGER 타입인 경우.
        int val = cursor.getInt(0) ;

        // 두 번째 컬럼(Column)의 타입이 TEXT 인 경우.
        String str = cursor.getText(1) ;

        // 세 번째 컬럼(Column)이 REAL 타입으로 선언된 경우.
        float real = cursor.getFloat(2) ;

        // 코드 계속...
    }

2.5.3 데이터베이스 데이터 조회하기. (SELECT)

앞서 설명한 쿼리(Query) 함수 및 커서(Cursor)를 사용하여 테이블에 저장된 모든 데이터를 조회하는 코드입니다.

[STEP-6] SQLiteDatabase - 데이터 조회. ("SELECT ... " 문을 rawQuery()로 실행.)
    String sqlSelect = "SELECT * FROM ORDER_T" ;
    Cursor cursor = null ;

    cursor = sqliteDB.rawQuery(sqlSelect) ;
    while (cursor.moveToNext()) {
        // INTEGER로 선언된 첫 번째 "NO" 컬럼 값 가져오기.
        int no = cursor.getInt(0) ;

        // TEXT로 선언된 두 번째 "NAME" 컬럼 값 가져오기.
        String name = cursor.getText(1) ;

        // TODO : use no, name.
    }

"SELECT" 문에 조건을 추가하여 특정 데이터만 조회하고자 한다면, 다른 SQL 문과 마찬가지로 "WHERE"를 추가하여 조건을 기술할 수 있습니다.

[STEP-6.1] SQLiteDatabase - 조건을 추가하여 데이터 조회. ("SELECT ... WHERE ..." 문을 rawQuery()로 실행.)
    // NAME 컬럼 값이 'ppotta'인 모든 데이터 조회.
    String sqlSelect = "SELECT * FROM ORDER_T WHERE NAME='ppotta'" ;

    Cursor cursor = sqliteDB.rawQuery(sqlSelect) ;
    while (cursor.moveToNext()) {
        // INTEGER로 선언된 첫 번째 "NO" 컬럼 값 가져오기.
        int no = cursor.getInt(0) ;

        // TEXT로 선언된 두 번째 "NAME" 컬럼 값 가져오기.
        String name = cursor.getText(1) ;

        // TODO : use no, name.
    }

2.6 SQLite 데이터베이스 테이블 삭제하기.

SQLite 데이터베이스에 만들어져 있는 테이블을 앱 실행 중 삭제하는 경우는 매우 드뭅니다. 하지만 앱의 기능이 변경되고 데이터베이스에 저장되는 데이터 구조가 복잡해지면, 기존에 만들어놓은 테이블만으로는 복잡해진 데이터를 처리하는 게 힘들어질 수 있습니다. 이 때 개발자가 선택할 수 있는 방법 중 하나는, 앱이 업그레이드된 후 처음 실행될 때 새로운 테이블을 만들어 기존 테이블의 데이터를 옮긴 다음, 기존 테이블은 삭제하는 것입니다.

 

테이블을 삭제하기 위해서는 "DROP TABLE" 문을 사용합니다.

[STEP-7] SQLiteDatabase - 테이블 삭제. ("DROP TABLE ... " 문을 execSQL()로 실행.)
    String sqlDropTbl = "DROP TABLE ORDER_T" ;

    sqliteDB.execSQL(sqlDropTbl) ;

"DROP TABLE" 문을 사용하여 테이블을 삭제할 때 주의해야 할 점은, 삭제하기 전 저장되어있던 데이터 또한 삭제와 동시에 모두 지워진다는 것과, 삭제된 데이터와 테이블은 다시 복구가 불가능하다는 것입니다. 그러므로 삭제하고자 하는 테이블에 중요한 데이터가 저장되어 있다면, 반드시 백업을 해둔 다음 테이블을 삭제해야 합니다.

3. 참고.

.END.

728x90
반응형
LIST

+ Recent posts