Android Code : รู้จักกับฐานข้อมูล Realm.io ตอนที่ 1
Realm.io database
บทความนี้อยากเขียนมานานแล้ว ได้โอกาสมาลองใช้ Realm ดูใช้งานง่าย และประทับใจมาก เลยมาบันทึกเกี่ยวกับ Realm ไว้
รู้จักกับ 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 ได้แล้ว
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 ไม่ถูกเรียกใช้งาน ต้องไปกำหนดให้มันทำงาน
มาที่ 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
จะได้คลาส 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>
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); } }
การอัพเดท
สามารถใช้ 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