Distributed Transaction Dengan JTA Di Aplikasi Desktop


Seiring dengan tibanya masa ujian akhir dan berakhirnya tahun ajar, begitu juga telah tiba saatnya bagi saya untuk meninggalkan aktifitas sebagai dosen dan kembali beraktifitas sebagai programmer.  Selama dua tahun menjadi pengajar memberikan banyak pengalaman berharga bagi saya, terutama mengajarkan pada saya bagaimana menyampaikan ide dan membimbing komunitas.

Pada kesempatan ini, saya akan mencoba menggunakan distributed transaction.  Contoh kondisi dimana kita menggunakan distributed transaction adalah pada saat  program harus menyimpan data di lebih dari satu database dan operasi penyimpanan tersebut harus merupakan satu transaksi tunggal.   Tanpa distributed transaction, kegagalan penyimpanan data di satu database tidak mempengaruhi penyimpanan data yang berhasil di database lain.   Padahal, bila data gagal disimpan di salah satu database, maka data di database lain seharusnya juga tidak jadi disimpan.  Database yang mendukung distributed transaction umumnya menyertakan driver X/Open XA (eXtended Architecture).

Pada aplikasi Java Enterprise Edition (JEE), distribution transaction diwakili dengan penggunakan Java Transaction API (JTA).  Hampir kebanyakan JEE container telah menyediakan implementasi dan dukungan JTA.    Apakah dengan demikian JTA hanya ekslusif milik aplikasi JEE (web)?  Tidak juga, aplikasi desktop standalone juga dapat menikmati penggunaan JTA.   Syaratnya adalah aplikasi tersebut harus menyertakan  implementasi JTA.  Salah satu implementasi JTA yang dapat dipakai oleh pembuat aplikasi desktop adalah Atomikos TransactionEssentials (http://www.atomikos.com/Main/ProductsOverview).

Saya akan memakai dua instance database MySQL, sebuah database berada di komputer lokal dan sebuah database lagi berada di komputer berbeda dengan nama host mysqltest (tetap dalam jaringan yang sama).

Pada database MySQL yang berada di komputer lokal, saya membuat sebuah tabel di database dengan nama latihan seperti berikut ini:

CREATE TABLE log (aktifitas VARCHAR(150));

Pada database MySQL yang berada di komputer mysqltest, saya membuat sebuah tabel di database dengan nama app seperti berikut ini:

CREATE TABLE item (keterangan VARCHAR(200));

Setelah itu, saya membuat sebuah proyek Maven dengan NetBeans.  Saya menambahkan dependency ke jta-1.1, mysql-connector-java, dan transactions-jdbc (atomikos).    Karena memakai Maven, NetBeans secara otomatis akan men-download JAR yang dibutuhkan bila belum ada di repository lokal.    Berikut ini adalah gambar yang memperlihatkan dependencies di proyek Maven saya:

Dependencies Proyek

Dependencies Proyek

Saya juga membuat sebuah file log4j.properties.  Karena NetBeans tidak akan menyertakan file selain file *.class di Source Packages, bila saya meletakkan file ini di Source Packages, maka pada hasil akhir program (JAR), file ini akan ‘hilang’.  Oleh sebab itu, saya membuat sebuah direktori baru dengan nama resources di lokasi src/main dan meletakkan file log4j.properties disana. Isi file log4j.properties yang saya buat adalah:

log4j.rootCategory=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n

Pada langkah berikutnya, saya membuat sebuah class baru dengan nama Koneksi, untuk mewakili koneksi ke database.  Isi file Koneksi.java adalah seperti berikut ini:

import com.atomikos.jdbc.AtomikosDataSourceBean;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

public class Koneksi {

  private AtomikosDataSourceBean dataSource;

  public Koneksi(String url, String user, String password, String uniqueName) {
    dataSource = new AtomikosDataSourceBean();
    dataSource.setUniqueResourceName(uniqueName);
    dataSource.setXaDataSourceClassName("com.mysql.jdbc.jdbc2.optional.MysqlXADataSource");
    Properties prop = new Properties();
    prop.setProperty("URL", url);
    prop.setProperty("user", user);
    prop.setProperty("password", password);
    dataSource.setXaProperties(prop);
  }

  public Connection getConnection() throws SQLException {
    return dataSource.getConnection();
  }

  public void close() {
    dataSource.close();
  }
}

Pada kode program ini, saya memakai class AtomikosDataSourceBean bawaan dari Atomikos.  Di AtomikosDataSourceBean, saya menyertakan informasi mengenai driver JDBC dan informasi lain yang berhubungan dengan koneksi saya.  Saya menggunakan driver XA MySQL yaitu “com.mysql.jdbc.jdbc2.optional.MysqlXADataSource“.  Lalu, saya dapat memperoleh sebuah Connection dengan memanggil method getConnection() dari AtomikosDataSourceBean.   Berikutnya, saya tinggal memakai Connection tersebut seperti  biasa di JDBC.

Pada class App (main class yang disediakan oleh NetBeans Maven), saya membuat kode program seperti berikut ini:

import com.atomikos.icatch.jta.UserTransactionManager;
import java.sql.Connection;

public class App {

  private App() {
    Koneksi koneksiLokal = new Koneksi("jdbc:mysql://localhost:3306/latihan", "user", "password", "MYSQLLOKAL");
    Koneksi koneksiRemote = new Koneksi("jdbc:mysql://mysqltest:3306/app", "user", "password", "MYSQLREMOTE");

    UserTransactionManager userTransaction = new UserTransactionManager();
    try {
      userTransaction.init();

      System.out.println("Membuat transaksi normal.. Record harus tersimpan di database lokal maupun remote.\n");
      userTransaction.begin();
      try (Connection cn = koneksiLokal.getConnection()) {
        cn.createStatement().executeUpdate("INSERT INTO log VALUES ('LOG1')");
      }
      try (Connection cn = koneksiRemote.getConnection()) {
        cn.createStatement().executeUpdate("INSERT INTO item VALUES ('ITEM1')");
      }
      userTransaction.commit();

      System.out.println("Mensimulasikan rollback.. Record harus TIDAK tersimpan di database lokal maupun remote.");
      userTransaction.begin();
      try (Connection cn = koneksiLokal.getConnection()) {
        cn.createStatement().executeUpdate("INSERT INTO log VALUES ('LOG1')");
      }
      try (Connection cn = koneksiRemote.getConnection()) {
        cn.createStatement().executeUpdate("INSERT INTO item VALUES ('ITEM1')");
      }
      System.out.println("Rollback dilakukan setelah memasukkan data di database lokal dan database remote.\n");
      userTransaction.rollback();

      System.out.println("Mensimulasikan kesalahan... Record harus TIDAK tersimpan di database lokal maupun remote.");
      userTransaction.begin();
      try (Connection cn = koneksiLokal.getConnection()) {
        cn.createStatement().executeUpdate("INSERT INTO log VALUES ('LOG1')");
      }
      System.out.println("Mensimulasikan kesalahan.");
      if (true) { // untuk mencegah compiler yang protes 'Unreachable statement'
        throw new Exception("ANGGAP SAJA ADA ERROR DISINI!");
      }
      try (Connection cn = koneksiRemote.getConnection()) {
        cn.createStatement().executeUpdate("INSERT INTO item VALUES ('ITEM1')");
      }
      userTransaction.commit();

    } catch (Exception ex) {
      ex.printStackTrace();
    } finally {
      koneksiLokal.close();
      koneksiRemote.close();
      userTransaction.close();
    }
 }

 public static void main(String[] args) {
   new App();
 }
}

Pada kode program diatas, saya membuat sebuah instance dari UserTransactionManager.  Class ini adalah class bawaan dari Atomikos yang merupakan implementasi JTA (men-implements interface javax.transaction.TransactionManager dan javax.transaction.UserTransaction).

Saya membuat tiga contoh kondisi pada program di atas, yaitu:

  1. Kondisi 1:  INSERT pada database lokal dan INSERT pada database remote.  Kemudian transaksi di-commit secara normal.  Harusnya, pada kedua database, record tersimpan dengan baik.
  2. Kondisi 2: INSERT pada database lokal dan INSERT pada database remote.  Kemudian transaski di-rollback.  UserTransactionManager harus membatalkan record yang ‘telah‘ di-insert secara otomatis, baik di database lokal maupun database remote.
  3. Kondisi 3: INSERT pada database lokal, lalu sebelum INSERT pada database remote dilakukan, tiba-tiba terjadi kesalahan.  UserTransactionManager harus membatalkan record yang ‘telah‘ di-insert di database lokal, sehingga tidak menimbulkan data yang tidak konsisten.

Perihal Solid Snake
I'm nothing...

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: