10 November 2012

สร้างฐานข้อมูล SQLite อย่างไรให้ไว?

Updated on

        สำหรับใครที่เริ่มต้นพึ่งจะหัดสร้างฐานข้อมูลให้อ่านของเก่าก่อนนะ [Android Code] SQLite บน Android เบื้องต้น (แอบละเอียด)

        ก่อนอื่นเลย คงจะรู้อยู่แล้วว่าบนแอนดรอยด์มี SQLite ให้ใช้ โดยที่ปกติมักจะสร้างฐานข้อมูลตอนที่เปิดแอพพลิเคชันเป็นครั้งแรกและในครั้งต่อไปก็ไม่ต้องสร้างแล้ว เปิดจากฐานข้อมูลมาใช้ต่อเลย

        แต่สำหรับข้อมูลที่เอามาเก็บในฐานข้อมูล SQLite นั้น ไม่ว่าจะเป็นการเพิ่มเข้าไปด้วยคำสั่งในโปรแกรมหรือจาก CSV [Android Code] การนำเข้าข้อมูลจาก .csv ลงในฐานข้อมูล

        ถ้าข้อมูลมีนิดหน่อยก็คงไม่เจอปัญหาอะไรซักเท่าไร (พันกว่าตัว) แต่ถ้าข้อมูลมีขนาดเยอะมาก การสร้างฐานข้อมูลจะใช้เวลานานมาก อย่างเช่นแอพคำศัพท์สำหรับ Dictionary ของ Lexitron  ที่มีคำศัพท์ประมาณ 8 หมื่นคำ มีคอลัมน์ 8 คอลัมน์ ข้อมูลโดยรวมแล้วตกประมาณ 10MB  กว่าๆเลยทีเดียว พวกนี้จะใช้เวลาเป็นสิบนาทีเลยทีเดียว 

        ทีนี้ลองนึกถึงผู้ใช้ที่โหลดแอพไปใช้ แล้วพบว่าเปิดแอพครั้งแรกต้องมานั่งรอสร้างฐานข้อมูลสิบกว่านาที ใครจะไปนั่งรอใช้กันล่ะ? ถ้าเป็นแอพเกมสนุกๆก็ว่าไปอย่าง ดังนั้นเจ้าของบล็อกจึงเขียนบทความนี้ขึ้นมาเพื่อพูดถึง "วิธีลัด" ในการสร้างฐานข้อมูลขนาดใหญ่ ให้ใช้เวลาน้อย

        สำหรับไฟล์ฐานข้อมูลจะสร้างจากไหนก็ได้ขอแค่ว่าเป็น SQLite หรือจะสร้างจาก SQLite Databases Browser ก้ได้

        เมื่อได้ไฟล์ฐานข้อมูลมาแล้วก็เอาไปไว้ในโปรเจคได้เลย โดยเอาไว้ใน assets จะดีกว่าเพราะว่าสามารถเรียก Path ได้เลย



        เมื่อได้ไฟล์ฐานข้อมูลมาพร้อมแล้ว เวลาเขียนโปรแกรมก็ทำการเช็คก่อน ว่ามีไฟล์ฐานข้อมูลแล้วหรือยัง โดยเช็คว่ามีไฟล์ใน data/data/package_name/databases/database_file หรือไม่ ถ้ายังไม่มีก็ให้ก๊อปไฟล์ฐานข้อมูลที่เตรียมไว้ไปวางแทน แต่ถ้ามีแล้วก็ข้ามไปให้แอพทำงานต่อตามต้องการเลย

       สรุปก็คือจะใช้วิธีสร้างไฟล์ฐานข้อมูลเตรียมเอาไว้เลยแล้วจะใช้คำสั่งโยนไฟล์ดังกล่าวไปไว้ในตำแหน่งที่ไฟล์ฐานข้อมูลอยู่ ทีนี้ก็ลองดูตัวอย่างเลยดีกว่า 


Main.java
package app.akexorcist.databasequicksqlite;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;

import android.os.Bundle;
import android.app.Activity;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.view.Window;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class Main extends Activity {
    SQLiteDatabase mDb;
    MyDbHelper mHelper;
    Cursor mCursor;
    
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.main);

        checkDatabase();

        ListView listView1 = (ListView)findViewById(R.id.listView1);
        ArrayList<String> dirArray = new ArrayList<String>();

        mHelper = new MyDbHelper(this);
        mDb = mHelper.getWritableDatabase();
        mCursor = mDb.rawQuery("SELECT * FROM " + MyDbHelper.TABLE_NAME, null);
        mCursor.moveToFirst();
        while(!mCursor.isAfterLast()){
            dirArray.add("Name : " 
                    + mCursor.getString(mCursor.getColumnIndex(MyDbHelper.COL_NAME))
                    + "\nPiece Price : " 
                    + mCursor.getString(mCursor.getColumnIndex(MyDbHelper.COL_PIECE_PRICE))
                    + "\nCake Price" 
                    + mCursor.getString(mCursor.getColumnIndex(MyDbHelper.COL_CAKE_PRICE)));
            mCursor.moveToNext();    
        }
        
        ArrayAdapter<String> adapterDir = new ArrayAdapter<String>(getApplicationContext() 
                , android.R.layout.simple_list_item_1, dirArray);
        listView1.setAdapter(adapterDir);
    }
    
    public void checkDatabase() {
        String url = "/data/data/" + getPackageName() + "/databases/BTS";
        File f = new File(url);
        if(!f.exists()) {
            try {
                mHelper = new MyDbHelper(this);
                mDb = mHelper.getWritableDatabase();
                mDb.close();  
                mHelper.close();
                InputStream in = getAssets().open("BTS");
                OutputStream out = new FileOutputStream(url);
                byte[] buffer = new byte[in.available()];
                in.read(buffer);
                out.write(buffer, 0, buffer.length);
                in.close();
                out.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    public void onPause() {
        super.onPause();
        mHelper.close();
        mDb.close();  
    }
}

        สำหรับคำสั่งใน Main.java ก็จะเหมือนกับคำสั่งสร้างฐานข้อมูลทั่วๆไปที่เอาข้อมูลในฐานข้อมูลมาแสดงบน List View แต่ว่าเจ้าของบล็อกจะมีการสร้างฟังก์ชัน checkDatabase ขึ้นมา ซึ่งเป็นฟังก์ชันตรวจสอบว่าไฟล์ฐานข้อมูลถูกสร้างขึ้นหรือยัง ทีนี้ขอข้ามไปอธิบายฟังก์ชัน checkDatabase เลยนะ สำหรับฟังก์ชันนี้ ก็จะสร้าง String ขึ้นมาเพื่อเก็บที่อยู่ของไฟล์ จะเห็นว่าเจ้าของบล็อกใช้ "/data/data/" + getPackageName() + "/databases/BTS" โดยที่ getPackageName() คือคำสั่งรับชื่อ Package ของแอพนั้นๆให้ทันที เวลาผู้ที่หลงเข้ามาอ่านเอาไปใช้ก็ไม่ต้องแก้ชื่อ Package เลย จากนั้นก็เรียกใช้คลาส File ขึ้นมา โดยใช้ URL จากตัวแปร String เมื่อกี้

        ซึ่งก็คือ Object ตัวนี้จะ Path ไปยังไฟล์ที่กำหนดเป็น URL แล้วทำการเช็คด้วย if ว่ามีไฟล์ดังกล่าวอยู่หรือไม่ด้วยคำสั่ง f.exists() โดยใช้ ! ด้วย เพราะว่าจะให้ทำเงื่อนไขเมื่อไม่มีไฟล์นี้อยู่ เมื่อเข้าเงื่อนไขว่ายังไม่มีไฟล์ฐานข้อมูลอยู่ในเครื่องก็ให้เรียกใช้คลาสฐานข้อมูลเพื่อสร้างฐานข้อมูลเปล่าๆก่อน แล้วก็ปิดฐานข้อมูลซะ ไม่งั้นจะวางไฟล์ทับไม่ได้เพราะเปิดใช้งานอยู่ จากนั้นก็เรียกใช้คลาสของ InputStream กับ OutputStream เพื่อใช้สำหรับนำไฟล์ฐานข้อมูลที่เตรียมไว้ไปวางทับฐานข้อมูลเปล่า เท่านี้ก็เสร็จเรียบร้อยแล้ว


MyDbHelper.java
package app.akexorcist.databasequicksqlite;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

class MyDbHelper extends SQLiteOpenHelper {
    private static final String DB_NAME = "BTS";
    private static final int DB_VERSION = 1;
    
    public static final String TABLE_NAME = "Product";

    public static final String COL_NAME = "name";
    public static final String COL_PIECE_PRICE = "pieceprice";
    public static final String COL_CAKE_PRICE = "cakeprice";
    
    public MyDbHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    public void onCreate(SQLiteDatabase db) { }
    
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
        onCreate(db);
    }
}

        สำหรับคลาสนี้ก็ไม่มีอะไร แค่คลาส Database ปกติ โดยรวมของคลาสนี้ก็มีแค่สร้างฐานข้อมูลเปล่าๆ ที่ไม่มีตารางและฐานข้อมูลใดๆเลย เพราะจะนำไฟล์ฐานข้อมูลมาวางทับ


main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" 
    android:background="#666666" >
    <TextView
        android:id="@+id/textView1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Cake Menu" 
        android:textColor="#222222" 
        android:textSize="40dp" 
        android:gravity="center" 
        android:layout_marginTop="20dp" />
    <ListView
        android:id="@+id/listView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" 
        android:layout_margin="30dp" 
        android:cacheColorHint="#00000000" />
</LinearLayout>


AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="app.akexorcist.databasequicksqlite"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="15" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".Main"
            android:label="@string/title_activity_main" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

        เรียบร้อยแล้ว ลองเอาไปทดสอบดูก็จะพบว่าไวกว่าเดิมเยอะ

        สำหรับผู้ที่หลงเข้ามาอ่านคนใดต้องการดาวน์โหลดไฟล์ตัวอย่างสามารถดาวน์โหลดได้จากที่นี่เลย Database Quick SQLite [Google Drive]


        ลองเอาวิธีนี้ไปใช้ดูละกันนะ