web analytics

Android Code : รู้จักกับฐานข้อมูล Realm.io ตอนที่ 1

cover-1

 

 

Realm.io database

บทความนี้อยากเขียนมานานแล้ว ได้โอกาสมาลองใช้ Realm ดูใช้งานง่าย และประทับใจมาก เลยมาบันทึกเกี่ยวกับ Realm ไว้

 

7-1

รู้จักกับ Realm

 

Realm.io เป็น Mobile database ที่มีการออกแบบ DBMS เอง Desgin สำหรับ Mobile ทั้งเร็ว มีประสิทธิภาพ และใช้งานง่าย แถม support cross platform เช่น  Objective-C, Swift, React Native, Android , Xamarin

มาดูความสามารถของ Realm

สามารถ insert 20,000 record ต่อ วินาที

พวก SQLite อ่อนแอก็แพ้ไป

 

หากมี 100,000 record Realm บอกว่า สามารถหาที่ตรงกันได้ ใน 1 วินาทีจะพบ 66 ตัว ส่วน SQLite ก็ได้แค่ 7 ตัว

การ Query ก็มีประสิทธิทิ้งขาดเพื่อนๆ หลายเท่าตัว

Realm VS SQLite

คะแนน Benchmarks ก็ชนะ SQLite ทุกการแข่งขัน

 

เริ่มต้น

มาลองใช้งาน Realm บน Android กันดีกว่า

ที่ build.gradle ระดับ project เพิ่ม dependency ของ realm plugin ลงไป

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        ..
        classpath 'io.realm:realm-gradle-plugin:1.2.0'
    }
}

 

ที่ build.gradle ระดับโมดูล app ให้เพิ่ม apply plugin ของ realm

..
apply plugin: 'realm-android'

android {
    ..
}

 

แค่นี้ก็สามารถใช้งาน Realm.io ได้แล้ว

1

 

Custom Application สำหรับ Realm

เพิ่ม Realm ใน application class
ให้สร้างคลาสขึ้นมา 1 ตัว แล้ว extend Application
จากนั้นก็ กำหนด RealmConfiguration ไว้ใน onCreate() โดยคำสั่งนี้จะทำงานในตอนแอปเริ่มทำงาน
ตรงชื่อฐานข้อมูล จะกำหนดอะไรก็ได้นะ แค่ไม่ว่าง กับ null ก็พอ

public class MyApplication extends Application{
    @Override
    public void onCreate() {
        super.onCreate();
        initRealm(); 
    }

    private void initRealm() {
        RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(this)
                .name("android.realm")
                .schemaVersion(0)
                .deleteRealmIfMigrationNeeded()
                .build();
        Realm.setDefaultConfiguration(realmConfiguration);
    }
}

 

ต่อมา คลาส MyApplication ไม่ถูกเรียกใช้งาน ต้องไปกำหนดให้มันทำงาน

2

มาที่ AndroidManifest.xml ใส่ name ให้มันเป็น application ที่เราพึ่งสร้างไว้

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.benzneststudios.myrealmproject">
    <application
        android:name=".MyApplication"
        android:allowBackup="true"
        ..
    </application>
</manifest>

 

สร้างคลาส Model

ตัวอย่างนี้จะทำฐานข้อมูลเกี่ยวกับข้อมูลนักเรียน (ชอบนักศึกษา) สร้างคลาส Student สำหรับเป็นโมเดลของข้อมูล
สิ่งสำคัญคือ
extend RealmObject

ให้พิมพ์แค่พวก field ก็พอ ส่วน method setter-getter ให้ Android Studio มันสร้างให้
คือกดปุ่ม Alt + Insert แล้วเลือก Getter and Setter

3

จะได้คลาส Student

public class Student extends RealmObject {
    @PrimaryKey
    int studentId;

    @Required
    String studentName;

    int studentScore;

    public int getStudentId() {
        return studentId;
    }

    public void setStudentId(int studentId) {
        this.studentId = studentId;
    }

    public String getStudentName() {
        return studentName;
    }

    public void setStudentName(String studentName) {
        this.studentName = studentName;
    }

    public int getStudentScore() {
        return studentScore;
    }

    public void setStudentScore(int studentScore) {
        this.studentScore = studentScore;
    }
}

การใช้ Annotation

@Required คือ กำหนดว่าห้ามเป็นค่า null ซึ่งจะใช้กับพวก integer ไม่ได้

@PrimaryKey คือ กำหนดให้ค่านั้นใน field มีได้ค่าเดียวไม่มีซ้ำ เช่นพวก id ทั้งหลาย

@Index คือ การเพิ่ม search index จะทำให้ตอน Query เร็วขึ้นมาก แต่จะแลกกับเวลาในการ insert ที่มากขึ้น
รองรับ String, byte, short, int, long, boolean, Date

@Ignore คือ กำหนดว่าที่ไม่ต้องเก็บลง database

ดังนั้นผมจึงกำหนดให้ studentId เป็น @Primary ส่วนค่าที่ห้าม null ก็ใส่ @Required

 

Field types

รองรับชนิด type ดังนี้ใน database

boolean, byte, short, int, long, float, double, String, Date, byte[].

ตัวเลขพวก integer เช่น  byte, short, int, long จะถูกแปลงเป็นชนิด long ทั้งหมดในฐานข้อมูล

 

การใช้งาน Realm

มาที่ Activity หรือ Fragment ให้ประกาศตัวแปร Realm

private Realm realm;

จากนั้นกำหนดค่า ก่อนใช้งาน เช่น กำหนดไว้ที่ onCreate()

realm = Realm.getDefaultInstance();

และปิดการใช้งาน realm เมื่อ Activity ถูกทำลาย

    @Override
    protected void onDestroy() {
        super.onDestroy();
        realm.close();
    }

สำหรับ Fragment onDestroy() อาจจะไม่ถูกเรียก ดังนั้นควรทำที่ onStop() แทน

    @Override
    public void onStop() {
        super.onStop();
        realm.close();
    }

การทำงานแบบ Synchronous

การทำงานแบบ synchronous จะทำให้ UI Thread ถูกหยุดชั่วคราว เพราะต้องรอการทำงานนี้เสร็จก่อน
ถ้าเป็นงานที่ไม่หนักมากก็จะไม่เห็นผลกระทบ

            realm.executeTransaction(
                new Realm.Transaction() {
                    @Override
                    public void execute(Realm realm) {
                        // Do something,
                    }
            }

 

การทำงานแบบ Asynchronous

การทำงานแบบ Async นี้ UI Thread จะไม่ถูกรบกวน เพราะเหมือนเป็นการทำงานเบื้องหลัง
เหมาะกับงานทำอะไรที่ต้องรอ เราจะรู้ผลการทำงานได้ผ่าน Callback คือ onSuccess และ onError

       realm.executeTransactionAsync(
                new Realm.Transaction() {
                    @Override
                    public void execute(Realm realm) {
                        // do something.
                    }
                }, new Realm.Transaction.OnSuccess() {
                    @Override
                    public void onSuccess() {
                        // When task complete success.
                    }
                }, new Realm.Transaction.OnError() {
                    @Override
                    public void onError(Throwable error) {
                        error.printStackTrace();
                        // When something error.
                    }
                });

ทั้งนี้ จะใช้ sync หรือ Async ก็อยู่ที่ความเหมาะสมครับ

 

การอ่านจากฐานข้อมูล (Select)

การ select จะ return เป็น RealmResults<Class> ออกมา
ตัวอย่างการ ดึงข้อมูลนักเรียนออกมาทั้งหมด

    public RealmResults<Student> getAllStudent() {
        RealmResults<Student> result = realm.where(Student.class).findAll();
        return result;
    }

เดี๋ยวการใช้งานแบบซับซ้อนขึ้นจะอธิบายในตอนต่อไปครับ

 

การเพิ่มลงฐานข้อมูล (Insert)

การ insert ปกติ สามารถใช้ synchronous แบบนี้ได้ เช่น เพิ่มนักเรียนแบบ 1 คน

private void insertStudent() {
      realm.beginTransaction();

      Student student = realm.createObject(Student.class);
      student.setStudentId( id );
      student.setStudentName( name );
      student.setStudentScore( score );
      realm.commitTransaction();
}

การลบทั้งหมด (Delete All)

การลบทั้งหมดใช้ realm.delete(class) ได้

    private void clearStudent() {
        realm.beginTransaction();
        realm.delete(Student.class);
        realm.commitTransaction();
    }

 

ลองทำ Workshop

จะมาลองใช้งาน Realm กัน สร้างปุ่มสักปุ่มนึง ใน layout ซึ่งกดแล้ว ถ้ามีข้อมูลจะทำการลบทิ้งแล้วทำการสุ่มข้อมูลนักเรียน 100 คนใหม่ จากนั้นจะ insert ข้อมูลลงฐานข้อมูล พร้อมแสดงออกมาเป็น text ที่ EditText ง่ายๆแค่นี้
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context="com.benzneststudios.myrealmproject.MainActivity">
    
    <Button
        android:id="@+id/btn_generate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="16dp"
        android:text="Generate 100 students"
        android:textSize="18sp"/>

    <EditText
        android:id="@+id/edt_data"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:inputType="textMultiLine"
        />
</LinearLayout>

5

 

MainActivty.java

    Button btnGenerate;
    EditText edtData;

findViewById กำหนด onClick ของปุ่ม

@Override
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        realm = Realm.getDefaultInstance();

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

        edtData = (EditText) findViewById(R.id.edt_data);
}

คลิกปุ่มแล้วให้ลบข้อมูลเก่าออกแล้วสุ่มใหม่

    private void generateStudent(){
        clearStudent();
        insertStudent();
    }

ในตัวอย่างนี้จะสุ่มข้อมูลออกมาและ insert ลงไป 100 คน จึงขอใช้แบบ Async
เริ่มจาก สร้าง method สำหรับสุ่มข้อมูลก่อน

    public ArrayList<Student> getSampleStudentData(int size) {
        String name[] = {"Raj Koothrappali", "Penny Hofstadter", "Leonard Hopstater", "Sheldon cooper", "Howard Wolowitz"};
        ArrayList<Student> listStudent = new ArrayList<>();
        for (int i = 1; i <= size; i++) {
            Student student = new Student();
            student.setStudentId(i);
            student.setStudentName(name[i % name.length]);
            student.setStudentScore((int) (Math.random() * 100));
            listStudent.add(student);
        }
        return listStudent;
    }

แล้วก็มาเขียน ที่เรียกให้สุ่มข้อมูลนักเรียนมา 100 คน แล้วให้วนลูปพิ่มลงฐานข้อมูลแบบ Aysn

private void insertStudent() {       
        final ArrayList<Student> listStudentGenerate = getSampleStudentData(100);

        realm.executeTransactionAsync(
                new Realm.Transaction() {
                    @Override
                    public void execute(Realm realm) {
                        for (final Student s : listStudentGenerate) {
                            Student student = realm.createObject(Student.class);
                            student.setStudentId(s.getStudentId());
                            student.setStudentName(s.getStudentName());
                            student.setStudentScore(s.getStudentScore());
                            Log.d("STUDENT", "Add student id = " + s.getStudentId());
                        }
                    }
                }, new Realm.Transaction.OnSuccess() {
                    @Override
                    public void onSuccess() {
                        Toast.makeText(getApplicationContext(), "Generate student complete.", Toast.LENGTH_SHORT).show();
                    }

                }, new Realm.Transaction.OnError() {
                    @Override
                    public void onError(Throwable error) {
                        error.printStackTrace();
                        Toast.makeText(getApplicationContext(), "", Toast.LENGTH_SHORT).show();
                    }
                });
}

 

ที่ onSuccess กำหนดให้มันแสดงข้อมูลหลังจาก insert เรียบร้อยแล้ว

realm.executeTransactionAsync(
                new Realm.Transaction() {
                    @Override
                    public void execute(Realm realm) {
                        for (final Student s : listStudentGenerate) {
                            Student student = realm.createObject(Student.class);
                            student.setStudentId(s.getStudentId());
                            student.setStudentName(s.getStudentName());
                            student.setStudentScore(s.getStudentScore());
                            Log.d("STUDENT", "Add student id = " + s.getStudentId());
                        }
                    }
                }, new Realm.Transaction.OnSuccess() {
                    @Override
                    public void onSuccess() {
                        Toast.makeText(getApplicationContext(), "Generate student complete.", Toast.LENGTH_SHORT).show();
                        showStudent();
                    }

                }, new Realm.Transaction.OnError() {
                    @Override
                    public void onError(Throwable error) {
                        error.printStackTrace();
                        Toast.makeText(getApplicationContext(), "", Toast.LENGTH_SHORT).show();
                    }
                });

หลังจาก insert เสร็จแล้วให้ทำการแสดงออกมาเป็น text ใน editText

    private void showStudent() {
        RealmResults<Student> listStudent = getAllStudent();
        Log.d("STUDENT", "SIZE = " + listStudent.size());
        String str = "";
        for (Student student : listStudent) {
            str += "\nID = " + student.getStudentId() +
                    " , " + student.getStudentName() +
                    " , (" + student.getStudentScore() + ")";
            edtData.setText(str);
        }
    }

 

6-1

 

การอัพเดท

สามารถใช้ copyToRealmOrUpdate() ได้เลย โดยโยน object ให้มันแล้วมันจะดูจาก primary key

    private void updateStudent(){
        final Student student = new Student();
        student.setStudentId(1);
        student.setStudentName("Benznest");
        realm.executeTransaction(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                realm.copyToRealmOrUpdate(student);
            }
        });
    }

 

Source code

https://gist.github.com/benznest/dd106f3bcb46b136e365e53370533b0e

 

บทความนี้ก็ได้รู้จักกับการติดตั้ง Realm และใช้งานคร่าวๆเบื้องต้น
บทความตอนหน้าจะ ลองทำ workshop ที่ใช้งานซับซ้อนขึ้นครับ

 

Reference

https://realm.io/news/realm-for-android/
http://www.trydroid.com/2015/12/android-realm-mobile-database.html