Menambahkan Fungsi Backup Untuk MySQL Pada Aplikasi


Salah satu fitur yang sering dibutuhkan pada aplikasi bisnis adalah backup.  Dengan melakukan backup secara teratur, pengguna bisa menghindari resiko kehilangan data.  Pada tulisan ini, saya akan mencoba menambahkan fasilitas backup manual pada aplikasi yang saya buat.  Aplikasi ini berjalan pada sistem operasi Windows dan dikembangkan dengan menggunakan Griffon dan plugin simple-jpa 0.4.2 serta memakai database MySQL.

Saya akan mulai dengan membuat sebuah MVCGroup baru:

griffon create-mvc backup

Setelah itu, saya membuat kode program untuk BackupView.groovy seperti yang terlihat berikut ini:

import net.miginfocom.swing.MigLayout
import javax.swing.BorderFactory
import javax.swing.JFileChooser

def fileChooser = fileChooser(fileSelectionMode: JFileChooser.DIRECTORIES_ONLY)

application(title: 'latihan',
  preferredSize: [320, 240],
  pack: true,
  locationByPlatform: true) {

    panel(id: 'mainPanel', border: BorderFactory.createEmptyBorder(5,5,5,5)) {
        borderLayout()
        panel(layout: new MigLayout('', '[left][left]', ''), constraints: PAGE_START) {
            label('Lokasi MySQL: ')
            label(text: bind('basedir', source: model), constraints: 'wrap')

            label('Lokasi Data: ')
            label(text: bind('datadir', source: model), constraints: 'wrap')

            label('Lokasi Tujuan: ')
            label(text: bind('lokasiTujuan', source: model), constraints: 'split 2')
            button('Pilih Lokasi', constraints: 'gapleft 10px, wrap', actionPerformed: {
                if (fileChooser.showOpenDialog(view.mainPanel)==JFileChooser.APPROVE_OPTION) {
                    model.lokasiTujuan = fileChooser.selectedFile
                }
            })

            button('Mulai Backup', actionPerformed: controller.mulai, constraints: 'gaptop 10px, wrap')
        }

        scrollPane(constraints: CENTER) {
            textArea(id: 'output', editable: false)
        }

    }
}

Tampilan di atas membutuhkan BackupModel.groovy dengan isi seperti berikut ini:

class BackupModel {

  @Bindable String datadir
  @Bindable String basedir
  @Bindable File lokasiTujuan

}

Kemudian, pada BackupController.groovy, saya menambahkan kode program inisialisasi seperti berikut ini:

void mvcGroupInit(Map args) {
  init()
}

@SimpleJpaTransaction(newSession=true)
def init = {
  model.datadir = executeNativeQuery("SHOW VARIABLES LIKE 'datadir'")[0][1]
  model.basedir = executeNativeQuery("SHOW VARIABLES LIKE 'basedir'")[0][1]
  model.lokasiTujuan = File.listRoots().last()
}

Perhatikan bahwa saya tidak menambahkan @SimpleJpaTransaction pada deklarasi class, dan hanya menambahkannya pada closure init.  Hal ini karena hanya closure init yang perlu dijalankan dalam transaction karena ia akan melakukan query ke database.  executeNativeQuery() adalah method dari simple-jpa yang memungkinkan pengguna untuk mengerjakan SQL.  Sebagai contoh, perintah SHOW VARIABLES  di atas adalah contoh perintah yang tidak dapat dikerjakan melalui JP QL sehingga harus menggunakan executeNativeQuery().  Kembalian dari method ini adalah List yang berisi array yang mewakili setiap kolom yang ada.

Perintah SHOW VARIABLES sebenarnya bukanlah bagian dari SQL melainkan perintah khusus MySQL Server untuk melihat konfigurasi pada database tersebut.  Perintah SHOW VARIABLES LIKE 'datadir' akan mengembalikan lokasi dimana MySQL Server menyimpan database-nya dalam bentuk file.  Perintah SHOW VARIABLES LIKE 'basedir' akan mengembalikan lokasi dimana MySQL Server di-install.

Kode program File.listRoots() akan mengembalikan List yang berisi seluruh root directory.  Pada sistem operasi turunan UNIX, hanya satu root.  Hal ini berbeda dengan Windows, dimana terdapat banyak root, seperti C:\ untuk harddisk, D:\ untuk drive optik, dan sebagainya.

Sebagai contoh, pada instalasi default MySQL, bila program dijalankan, saya akan menemukan tampilan seperti berikut ini:

Tampilan Awal

Tampilan Awal

Lalu, bagaimana cara backup yang baik?

Apakah dapat langsung men-copy seluruh isi datadir yang ada di database MySQL?  Pada database yang memakai engine MyISAM, hal ini dapat dilakukan.  Walaupun dapat diterapkan pada database yang memakai engine InnoDB,  hal ini tidak disarankan!  Selain itu, saya perlu memastikan bahwa server database sudah dimatikan sebelum men-copy file data.  MySQL versi enterprise (berbayar!) memiliki fasilitas hot backup sehingga proses backup dapat dilakukan saat database masih berjalan;  hot backup juga akan menyertakan perubahan pada database saat backup sedang dikerjakan.

Pada kesempatan ini, saya akan mencoba mysqldump yang lebih lambat.  Berbeda dengan cara di atas yang melakukan physical backup, mysqldump akan melakukan logical backup.  Hasil hasil dari logical backup adalah perintah SQL yang mewakili seluruh data yang ada.  Penggunaan logical backup tidak disarankan karena lebih lambat dibanding physical backup.

Sebagai contoh, saya akan membuat kode program berikut ini pada BackupController.groovy:

def mulai = {
    JTextArea output = view.output
    String userName = SimpleJpaUtil.instance.dbUsername
    String password = SimpleJpaUtil.instance.dbPassword
    String dbName = SimpleJpaUtil.instance.dbName

    String namafile = DateTime.now().toString('yyyyMMdd-hhmm')
    String lokasiTujuan = model.lokasiTujuan.absolutePath
    Path fileBackup = Paths.get(lokasiTujuan, "${namafile}-${dbName}-backup.sql")
    Path fileError = Paths.get(lokasiTujuan, "${namafile}-${dbName}-error.log")

    Files.newOutputStream(fileBackup, StandardOpenOption.CREATE_NEW).withStream { outputFile ->
        Files.newOutputStream(fileError, StandardOpenOption.CREATE_NEW).withStream { errorFile ->

            String mySqlDump = Paths.get(model.basedir, "bin", "mysqldump").toString()
            execInsideUISync { output.append("Mulai mengerjakan $mySqlDump ...\n") }
            Process process = [mySqlDump, "-u${userName}", "-p${password}", "${dbName}"].execute()
            execInsideUISync { output.append("Backup sedang diproses. Harap sabar menunggu...\n")}

            process.consumeProcessOutput(outputFile, errorFile)
            process.waitFor()

            execInsideUISync {
                output.append("Proses backup selesai!\n")
                output.append("Hasil backup dapat ditemukan di ${fileBackup}\n")
                output.append("Pesan kesalahan selama backup dapat ditemukan di ${fileError}\n")
            }

        }
    }

    execInsideUISync { output.append("Selesai.\n\n") }
}

Pada kode program di atas, saya menggunakan Paths.get() dari Java NIO untuk menghasilkan lokasi path berdasarkan beberapa String yang terpisah.  Hal ini jauh lebih mudah bila dibandingkan untuk merangkai path secara manual karena pemisah path bisa berbeda tergantung platform (misalnya pada Windows berupa karakter ‘\’ sementara pada Unix berupa karakter ‘/’).  Selain itu, Paths.get() juga tetap dapat menghasilkan path yang benar walaupun beberapa String diakhiri karakter pemisah path dan beberapa lagi tidak.  Nama file yang dihasilkan akan mengikuti format tahun, bulan, tanggal lalu diikuti dengan jam menit selanjutnya nama database.

Untuk memanggil mysqladmin, saya memakai method execute() yang ditambahkan oleh Groovy untuk List.  Argumen untuk pemanggilan mysqladmin saya peroleh dari class SimpleJpaUtil milik simple-jpa yang menyediakan informasi seperti nama user, password, dan nama database yang dipakai.

Karena output dari eksekusi mysqladmin berupa SQL yang hendak di-backup, maka saya memakai Process.consumeProsessOutput() untuk segera menuliskan output ke file yang telah ditentukan.

Berikut ini adalah contoh tampilan setelah tombol Mulai di-klik:

Contoh Tampilan Setelah Tombol Mulai Backup Di-klik

Contoh Tampilan Setelah Tombol Mulai Backup Di-klik

Program di atas adalah contoh pemanggilan mysqldump secara manual.  Penerapan lain yang lebih user friendly adalah dengan men-schedule pemanggilan mysqldump ke Windows Scheduler atau cron Linux sehingga proses backup dapat berlangsung secara periodik dan otomatis.

Perihal Solid Snake
I'm nothing...

5 Responses to Menambahkan Fungsi Backup Untuk MySQL Pada Aplikasi

  1. Komang Hendra Santosa mengatakan:

    Mas, saya kok dapat error seperti ini ya?

    [griffonc] D:\latihan\inventory\griffon-app\controllers\project\BackupControlle
    r.groovy: 37: unexpected token: Files @ line 37, column 3.
    [griffonc] Files.newOutputStream(fileBackup, StandardOpenOption.CRE
    ATE_NEW).withStream { outputFile ->
    [griffonc] ^
    [griffonc]
    [griffonc] 1 error
    Compilation error: Compilation Failed

    kira2 kenapa ya mas?

  2. Komang Hendra Santosa mengatakan:

    Ok yang diatas sudah clear, tp muncul error seperti ini lagi ketika eksekusi program mas..

    2013-11-29 23:07:29,243 [pool-2-thread-2] ERROR griffon.util.GriffonExceptionHan
    dler – Uncaught Exception
    groovy.lang.MissingPropertyException: No such property: dbUsername for class: simplejp
    a.SimpleJpaUtil
    at project.BackupController$_closure2_closure4.doCall(BackupController.g
    roovy:29)
    at project.BackupController$_closure2_closure4.doCall(BackupController.g
    roovy)
    at org.codehaus.griffon.runtime.util.AbstractUIThreadHandler$1.run(Abstr
    actUIThreadHandler.java:41)
    2013-11-29 23:07:38,209 [AWT-EventQueue-0] INFO griffon.swing.SwingApplication
    – Shutdown is in process
    [delete] Deleting directory D:\latihan\inventory\staging\windows

    String userName = SimpleJpaUtil.instance.dbUsername
    String password = SimpleJpaUtil.instance.dbPassword
    String dbName = SimpleJpaUtil.instance.dbName

    untuk dbUsername, dbPassword, dbName apa ditangkap langsung oleh SimpleJpaUtil mas?

    mohon pencerahannya..

    oh ya, klo ada backup, restore database kan juga ada ya mas, hehe..

    • Solid Snake mengatakan:

      Method tersebut seharusnya sudah ditambahkan di simple-jpa 0.5. Coba periksa apakah proyek sudah memakai versi terbaru. Pada pengujian yang saya lakukan, tidak ada kesalahan pada method tersebut.

      Ini adalah langkah pengujian yang saya lakukan:

      Pada proyek yang memakai simple-jpa, saya menjalankan perintah griffon simple-jpa-console. Lalu, pada GroovyConsole yang muncul, saya mengetikkan kode program berikut:

      simplejpa.SimpleJpaUtil.instance.with {
        println "Nama user adalah $dbUsername"
        println "Password adalah $dbPassword"
        println "Nama database adalah $dbName"
      }
      

      Setelah saya menjalankan program di atas (dengan menekan Ctrl+R), output ditampilkan sesuai harapan.

      Kode program pada artikel ini menghasilkan backup dalam bentuk file SQL sehingga untuk men-restore, bisa menggunakan perintah mysql -u user -ppassword dbname < backup.sql.

      CATATAN: Cara backup/restore ini sebenarnya tidak direkomendasikan karena mempengaruhi kinerja database dan dapat memakan waktu lama pada database yang besar. Tidak ada pilihan lain karena tidak ada tool sejenis RMAN di Oracle Database pada MySQL versi gratis. Walaupun demikian, Oracle menambahkan fitur hot-backup dan incremental-backup pada MySQL versi enterprise (berbayar) yang dapat dilihat di https://www.mysql.com/products/enterprise/backup.html.

  3. Komang Hendra Santosa mengatakan:

    di article ini mas menggunakan versi 0.4.2 dan saya pun menggunakan hal yang sama tp mengapa ada pesan error seperti di atas ya mas?

    • Solid Snake mengatakan:

      Iya benar, setelah saya memeriksa kembali, pada versi 0.4.2 sudah ada class SimpleJpaUtil.

      Pesan kesalahan yang kamu jumpai menunjukkan bahwa pada class simplejpa.SimpleJpaUtil bawaan simple-jpa tidak ada method seperti getDbUsername(), getDbPassword(), dan sebagainya. Hal ini seharusnya tidak terjadi bila class SimpleJpaUtil yang kamu pakai adalah bawaan simple-jpa.

      Sebagai solusi alternatif, kamu dapat mengisi langsung nama user, password, dsb. Class SimpleJpaUtil di package simplejpa pada dasarnya akan membaca nama user, password, url dari yang kamu definisikan di persistence.xml.

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: