The Importance of Sharing

Mengenal Swing Worker


Pada tulisan saya kali ini, saya ingin membahas mengenai penggunaan swing worker pada aplikasi java berbasis desktop. Adakalanya aplikasi kita mengerjakan atau memproses long-running task. Katakanlah, mendownload sesuatu atau mengeksekusi suatu query yang datanya sangat banyak. Kemudian kita memiliki form dengan button yang mana button tersebut yang akan mengeksekusi task tersebut. Jika kita menggunakan single thread maka aplikasi kita akan memproses task tersebut dan menunggu hingga selesai. Karena single thread dan aplikasi kita menunggu task tersebut selesai dieksekusi maka aplikasi kita akan terlihat seperti membeku atau menjadi unresponsive. Hal ini bisa saja menyebabkan user menjadi salah sangka, dikiranya aplikasi kita error. Untuk itu, kita harus membuat thread terpisah yang khusus mengeksekusi task tersebut, sehingga aplikasi kita terlihat responsive.

Pada artikel ini saya menggunakan netbeans 8.0 dan jdk 1.7 serta apache maven. Kemudian untuk databasenya saya menggunakan MySQL. Skenarionya sederhana saja, saya akan ,melakukan query untuk membaca data yang sangat banyak dari database. Untuk itu, pertama saya buat dulu database dan tablenya, serta datanya saya isi menggunakan store procedure. Lebih  jelasnya perhatikan script dibawah ini.

CREATE DATABASE belajarswingworker;

USE belajarswingworker;

CREATE TABLE karyawan (
id bigint auto_increment primary key,
nama varchar(30),
alamat varchar(45),
tanggal_lahir date,
telephone varchar(20)
)
engine=innodb;

delimiter//
create procedure isiData()
begin
declare i int default 1;
while(i<10000) do
begin
insert into karyawan (nama, alamat, tanggal_lahir,telephone) values('I Gede Arya Wiratama','Jl Raya Celuk Sukawati Gianyar','1945-08-17','0819325112');
set i += i + 1;
end while;
end //
delimiter;

call isiData();

Selanjutnya saya buat project baru pada netbeans, pilih maven, pilih Java Application, beri nama project tersebut kemudian klik finish. Kemudian tambahkan dependency yaitu mysql connector pada pom.xml.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.latihan</groupId>
    <artifactId>BelajarSwingWorker</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>Belajar Swing Worker</name>
    <url>http://github.com/aryawiratama</url>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.34</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

Jika sudah selesai selanjutnya saya membuat kelas entity yang merepresentasikan tabel karyawan. Kelas entity ini saya beri nama Kelas Karyawan dan packagenya adalah com.latihan.belajarswingworker.domain;

package com.latihan.belajarswingworker.domain;

import java.util.Date;

/**
 *
 * @author Artha
 */
public class Karyawan {
    private long id;
    private String nama;
    private String alamat;
    private Date tanggalLahir;
    private String telephone;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getNama() {
        return nama;
    }

    public void setNama(String nama) {
        this.nama = nama;
    }

    public String getAlamat() {
        return alamat;
    }

    public void setAlamat(String alamat) {
        this.alamat = alamat;
    }

    public Date getTanggalLahir() {
        return tanggalLahir;
    }

    public void setTanggalLahir(Date tanggalLahir) {
        this.tanggalLahir = tanggalLahir;
    }

    public String getTelephone() {
        return telephone;
    }

    public void setTelephone(String telephone) {
        this.telephone = telephone;
    }
}

Kemudian seperti biasa saya membuat interface DaoKaryawan dan membuat implementasinya pada Kelas DaoKaryawanImpl.

package com.latihan.belajarswingworker.dao;

import com.latihan.belajarswingworker.domain.Karyawan;
import java.util.List;

/**
 *
 * @author Artha
 */
public interface Daokaryawan {
    public List<Karyawan> getKaryawans();
}

package com.latihan.belajarswingworker.daoimpl;

import com.latihan.belajarswingworker.dao.Daokaryawan;
import com.latihan.belajarswingworker.domain.Karyawan;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author Artha
 */
public class DaoImplKaryawan implements Daokaryawan{

    private Connection Connection;

    public DaoImplKaryawan(Connection connection) {
        this.Connection=connection;
    }

    @Override
    public List<Karyawan> getKaryawans() {
        List<Karyawan> karyawans = new ArrayList<Karyawan>();
        String sql="SELECT id, nama, alamat, tanggal_lahir,telephone from karyawan";
        Karyawan k = null;
        try{
            PreparedStatement statement = Connection.prepareStatement(sql);
            ResultSet rs = statement.executeQuery();
            while(rs.next()){
                k = new Karyawan();
                k.setId(rs.getLong(1));
                k.setNama(rs.getString(2));
                k.setAlamat(rs.getString(3));
                k.setTanggalLahir(rs.getDate(4));
                k.setTelephone(rs.getString(5));
                karyawans.add(k);
                k=null;
            }
        }
        catch(Exception e){
            e.printStackTrace();
        }
        return karyawans;
    }
}

Selanjutnya saya membuat kelas untuk membuat objek koneksi ke database serta membuat instance daripada DaoKaryawan tersebut.

package com.latihan.belajarswingworker.util;

import com.latihan.belajarswingworker.dao.Daokaryawan;
import com.latihan.belajarswingworker.daoimpl.DaoImplKaryawan;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Artha
 */
public class Koneksi {
    private static Connection connection;
    private static Daokaryawan daokaryawan;

    private  Koneksi(){

    }

    private static Connection getConnection() {
        if (connection==null){
            try {
                Class.forName("com.mysql.jdbc.Driver").newInstance();
                connection= DriverManager.getConnection("jdbc:mysql://localhost:3306/belajarswingworker", "root", "root");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return connection;
    }

    public static Daokaryawan getDaokaryawan() {
        if (daokaryawan==null){
            daokaryawan = new DaoImplKaryawan(getConnection());
        }
        return daokaryawan;
    }

}

Buatlah sebuah form dengan tampilan dibawah ini.
Form Swing WorkerPada form tersebut ada 3 buah tombol, 1 progressbar dan 1 buah table. Tombol “tampil data” berfungsi untuk mengeksekusi task berupa membaca data dari database menggunakan swing worker. Tombol “stop” digunakan untuk menghentikan thread swing worker dan tombol tanpa worker digunakan untuk mengeksekusi task yang sama tapi tanpa swing worker. Setelah form selesai didesain saya isi dengan code dibawah ini.

package com.latihan.belajarswingworker.form;

import com.latihan.belajarswingworker.dao.Daokaryawan;
import com.latihan.belajarswingworker.domain.Karyawan;
import com.latihan.belajarswingworker.util.Koneksi;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
import javax.swing.JOptionPane;
import javax.swing.SwingWorker;
import javax.swing.table.DefaultTableModel;

/**
 *
 * @author Artha
 */
public class TampilData extends javax.swing.JFrame {
    private Daokaryawan daokaryawan;
    private DefaultTableModel model;
    SwingWorker<Boolean,Karyawan> worker;
    /**
     * Creates new form TampilData
     */
    public TampilData() {
        initComponents();
        daokaryawan=Koneksi.getDaokaryawan();
        model = (DefaultTableModel) tblKaryawan.getModel();
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">
    private void initComponents() {

        jLabel1 = new javax.swing.JLabel();
        btnTampil = new javax.swing.JButton();
        progressBar = new javax.swing.JProgressBar();
        jScrollPane1 = new javax.swing.JScrollPane();
        tblKaryawan = new javax.swing.JTable();
        btnStop = new javax.swing.JButton();
        btnTanpaWorker = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        jLabel1.setFont(new java.awt.Font("Tahoma", 0, 18)); // NOI18N
        jLabel1.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        jLabel1.setText("Belajar Swing Worker");

        btnTampil.setText("Tampil Data");
        btnTampil.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnTampilActionPerformed(evt);
            }
        });

        progressBar.setStringPainted(true);

        tblKaryawan.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {

            },
            new String [] {
                "Nama", "Alamat", "Tanggal Lahir", "Telephone"
            }
        ) {
            Class[] types = new Class [] {
                java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.String.class
            };
            boolean[] canEdit = new boolean [] {
                false, false, false, true
            };

            public Class getColumnClass(int columnIndex) {
                return types [columnIndex];
            }

            public boolean isCellEditable(int rowIndex, int columnIndex) {
                return canEdit [columnIndex];
            }
        });
        jScrollPane1.setViewportView(tblKaryawan);

        btnStop.setText("Stop");
        btnStop.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnStopActionPerformed(evt);
            }
        });

        btnTanpaWorker.setText("Tanpa Worker");
        btnTanpaWorker.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnTanpaWorkerActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jLabel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 467, Short.MAX_VALUE)
                    .addComponent(progressBar, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
                        .addComponent(btnTampil)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(btnStop)
                        .addGap(48, 48, 48)
                        .addComponent(btnTanpaWorker)
                        .addGap(0, 0, Short.MAX_VALUE)))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addComponent(jLabel1)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(btnTampil)
                    .addComponent(btnStop)
                    .addComponent(btnTanpaWorker))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(progressBar, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 203, Short.MAX_VALUE)
                .addContainerGap())
        );

        pack();
    }// </editor-fold>                        

    private void btnTampilActionPerformed(java.awt.event.ActionEvent evt) {                                          

        worker = new SwingWorker<Boolean, Karyawan>() {

            @Override
            protected Boolean doInBackground() throws Exception {
                List<Karyawan> karyawans = daokaryawan.getKaryawans();
                int i = 0;
                int total = karyawans.size();
                int persen = 0;
                for(Karyawan k : karyawans ){
                    persen = (i * 100) / total;
                    System.out.println("Persen : " + persen + " | i : " + i);
                    progressBar.setValue(persen);
                    i++;
                    publish(k);
                    Thread.sleep(100);
                }
                return true;
            }

            @Override
            protected void process(List<Karyawan> chunks) {
                Karyawan karyawan=chunks.get(chunks.size()-1);
                model.addRow(new Object[]{karyawan.getNama(),karyawan.getAlamat(),karyawan.getTanggalLahir(),karyawan.getTelephone() });
                int indexRow = model.getRowCount()-1;
                int indexColumn = model.getColumnCount()-1;
                tblKaryawan.scrollRectToVisible(tblKaryawan.getCellRect(indexRow, indexColumn, true));
                tblKaryawan.setRowSelectionInterval(indexRow, indexRow);
            }

            @Override
            protected void done() {
                JOptionPane.showMessageDialog(null, "Process done..","Information",JOptionPane.INFORMATION_MESSAGE);
            }
        };
        worker.execute();
    }                                         

    private void btnStopActionPerformed(java.awt.event.ActionEvent evt) {
        if (worker!=null){
            worker.cancel(true);
            progressBar.setValue(100);
        }
    }                                       

    private void btnTanpaWorkerActionPerformed(java.awt.event.ActionEvent evt) {
        List<Karyawan> karyawans = daokaryawan.getKaryawans();
        for(Karyawan k : karyawans){
            model.addRow(new Object[]{k.getNama(),k.getAlamat(),k.getTanggalLahir(),k.getTelephone() });
            int indexRow = model.getRowCount()-1;
            int indexColumn = model.getColumnCount()-1;
            tblKaryawan.scrollRectToVisible(tblKaryawan.getCellRect(indexRow, indexColumn, true));
            tblKaryawan.setRowSelectionInterval(indexRow, indexRow);
        }
    }                                              

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        /* Set the Nimbus look and feel */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
         * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
         */
        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException ex) {
            java.util.logging.Logger.getLogger(TampilData.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
            java.util.logging.Logger.getLogger(TampilData.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            java.util.logging.Logger.getLogger(TampilData.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (javax.swing.UnsupportedLookAndFeelException ex) {
            java.util.logging.Logger.getLogger(TampilData.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        //</editor-fold>

        /* Create and display the form */
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new TampilData().setVisible(true);
            }
        });
    }

    // Variables declaration - do not modify
    private javax.swing.JButton btnStop;
    private javax.swing.JButton btnTampil;
    private javax.swing.JButton btnTanpaWorker;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JProgressBar progressBar;
    private javax.swing.JTable tblKaryawan;
    // End of variables declaration
}

Untuk melihat hasilnya, kita bisa lihat pada screen cast dibawah ini.

Pada screencast tersebut, percobaan pertama adalah mengeksekusi task menggunakan swing worker. Kita bisa lihat kalau aplikasinya masih responsive, layarnya masih bisa di resize. Percobaan kedua kalau tidak menggunakan swing worker maka aplikasi tidak responsive. Bisa dilihat pada form aplikasi dibuat fullscreen ada sedikit warna hitam, kemudian ketika selesai load data baru layar dapat dirubah sesuka hati. Percobaan ketiga mencoba ketika thread dihentikan oleh user. Mudah2an tulisan saya ini bermanfaat bagi pembaca sekalian. Untuk source codenya dapat diambil di github

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: