Koneksi Database H2 dengan JavaFX


‘Bout that bass, no treble I’m all about that bass
Database.. base.. base~

Eh.. ehm.. Selamat SORE!!

[PENGUMUMAN]
Berdasarkan hasil polling “tutorial yang diinginkan di Saungkode” selama 2 minggu terakhir, JavaFX menduduki peringkat teratas chart FTV Ampuh untuk kategori tutorial pilihan pemirsa Saungkoders versi Polydaddy.com dengan perolehan 23 suara :|.. Oleh karena itu, pada episode “Minggu Sore” kali ini saya masih akan masih membahas JavaFX. Topik tutorial pilihan saya adalah koneksi database dengan H2 dan sedikit bahasan seputar penggunaan TableView (menampilkan dan mengisi data) pada JavaFX. Kira – kira hasil akhirnya seperti ini :

ScreenShot031

Tentang H2 (Hypersonic 2)

Bosan dengan MySQL, saya memutuskan untuk mencari aplikasi database alternatif yang open source dan akhirnya menemukan H2. Setelah “mencicipi”-nya beberapa lama, impression yang saya dapatkan adalah hmm..lumayan.. KEREN!

Instalasinya mudah, disk footprint-nya hanya sebesar kurang lebih 1 MB, syntax query-nya mirip (atau memang?) SQL dan ringan. H2 ini bisa berjalan secara embedded di aplikasi berbasis java maupun pada mode client-server. Menurut official page-nya H2 juga bisa menjadi alternatif database SQLite pada perangkat Android (tapi saya belum coba)..

Keterangan selengkapnya bisa dibaca langsung di sini.

Instalasi H2

Untuk pengguna windows download saja yang versi executable dan jalankan installernya. Untuk pengguna sistem operasi lain bisa coba download yang versi bundle.

Note: bagi yang mengalami kesulitan instalasi boleh ditanyakan di kolom komentar.

Koneksi H2 di Java

Tutorial lengkapnya baca di sini

Untuk membuat koneksi baru dengan mode client-server cukup menggunakan kode seperti di bawah :

Class.forName("org.h2.Driver").newInstance();
	Connection connection = 
		DriverManager.getConnection("jdbc:h2:tcp://localhost/~/saungkode", "saungkode",  "");

Kode di atas akan membuka database bernama “saungkode” (atau membuat database bernama “saungkode” secara otomatis jika belum ada) yang letaknya di home (kalo windows biasanya C:\Windows\Users\[Nama_User]).. Dengan username “saungkode” dan password “”

Setting Database

Untuk kepentingan tutorial mari kita buat dulu database dan struktur tabelnya..

1. Buka H2 Console

ScreenShot032

2. Connect

3. Pada kolom SQL Statement masukan query berikut

Buat tabelnya

DROP TABLE IF EXISTS ELEMEN;
CREATE TABLE ELEMEN(
	ID_ELEMEN INT PRIMARY KEY AUTO_INCREMENT,
	JENIS_ELEMEN VARCHAR(50)
);
DROP TABLE IF EXISTS POKEMON;
CREATE TABLE POKEMON(
	ID_POKEMON INT PRIMARY KEY AUTO_INCREMENT,
	NAMA VARCHAR(50),
	ELEMEN_PRIMER INT,
	ELEMEN_SEKUNDER INT,
        CONSTRAINT ELEMEN_POKEMON_FK_1 FOREIGN KEY (ELEMEN_PRIMER ) REFERENCES ELEMEN (ID_ELEMEN),
        CONSTRAINT ELEMEN_POKEMON_FK_2 FOREIGN KEY (ELEMEN_SEKUNDER ) REFERENCES ELEMEN (ID_ELEMEN)
);

Data dummy

INSERT INTO ELEMEN VALUES(1, 'Grass');
INSERT INTO ELEMEN VALUES(2, 'Fire');
INSERT INTO ELEMEN VALUES(3, 'Water');
INSERT INTO ELEMEN VALUES(4, 'Ground');
INSERT INTO ELEMEN VALUES(5, 'Rock');
INSERT INTO ELEMEN VALUES(6, 'Ghost');
INSERT INTO ELEMEN VALUES(7, 'Phychic');
INSERT INTO ELEMEN VALUES(8, 'Fighting');
INSERT INTO ELEMEN VALUES(9, 'Poison');
INSERT INTO ELEMEN VALUES(10, 'Normal');
INSERT INTO ELEMEN VALUES(11, 'Flying');
INSERT INTO ELEMEN VALUES(12, 'Steel');
INSERT INTO ELEMEN VALUES(13, 'Thunder');
INSERT INTO ELEMEN VALUES(14, 'Bug');

INSERT INTO POKEMON VALUES(1, 'Pikachu', 13, null);
INSERT INTO POKEMON VALUES(2, 'Squirtle', 3, null);
INSERT INTO POKEMON VALUES(3, 'Moltres', 2, 11);
INSERT INTO POKEMON VALUES(4, 'Beedril', 14, 9);
INSERT INTO POKEMON VALUES(5, 'Metacross', 12, 7);

4. Pastikan dulu datanya sudah masuk

SELECT * FROM ELEMEN;
SELECT * FROM POKEMON;

ScreenShot033

Aplikasi JavaFX

Fiuuh, akhirnya.. sekarang kita beralih ke JavaFX

1. Data Models

Karena kali ini yang kita pelajari adalah TableView, maka kita memerlukan class khusus yang bertindak sebagai model datanya. Data yang akan dimuat ke tabel adalah data pokemon yang sudah kita buat sebelumnya di database, jadi class datanya kurang lebih seperti ini :


public static class Pokemon
{
	private final SimpleStringProperty ID, nama, elemen_1, elemen_2;
	private Pokemon(String ID, String nama, String elemen_1, String elemen_2)
	{
		this.ID = new SimpleStringProperty(ID);
		this.nama = new SimpleStringProperty(nama);
		this.elemen_1 = new SimpleStringProperty(elemen_1);
		this.elemen_2 = new SimpleStringProperty(elemen_2);
	}
	public String getID() 
	{
		return ID.get();
	}
	public void setID(String fID)
	{
		ID.set(fID);
	}
	public String getNama() 
	{
		return nama.get();
	}
	public void setNama(String fNama)
	{
		nama.set(fNama);
	}
	public String getElemen_1() 
	{
		return elemen_1.get();
	}
	public void setElemen_1(String fElemen_1)
	{
		elemen_1.set(fElemen_1);
	}
	public String getElemen_2() 
	{
		return elemen_2.get();
	}
	public void setElemen_2(String fElemen_2)
	{
		elemen_2.set(fElemen_2);
	}
}

Harus banget pake class gini ya? IYA!. Bahkan dokumentasi oracle-nya sendiri menganjurkan cara seperti ini. Oiya, jangan lupa membuat getter dan setter untuk class tersebut dengan penamaan seperti di atas [get/set][nama_variabel] untuk [nama_variabel] harus diawali huruf besar, contoh untuk getter variable “elemen_1” jadinya getElemen_1(). Karena getter dan setter ini akan dipanggil oleh class TableColumn nantinya.

Apa itu SimpleStringProperty? Kenapa ga String biasa aja? Lagi-lagi alasannya adalah data yang dibutuhkan oleh TableView dan TableColumn mudahnya memiliki format seperti itu. Dan sampai saat ini saya belum mencari menemukan cara lain.

2. Buat TableView dan ComboBox yang bisa diakses oleh prosedur lain di luar prosedur utama

private final TableView<Pokemon> t_pokemon   = new TableView<>();
private final ComboBox<String> cb_elemen_1 	 = new ComboBox<String>(),
							   cb_elemen_2	 = new ComboBox<String>();

Note : TableView<Pokemon> artinya Table berisi data model kita yaitu dari class Pokemon

3. Buat observer list milik tabel pokemon dengan ObservableList

private final ObservableList<Pokemon> t_data = FXCollections.observableArrayList();

Class ini kita inisiasi dengan FXCollections.observableArrayList() untuk memberlakukan observer sebagai ArrayList. Tunggu, apaan lagi tuh ObservableList -_- ? Beliau adalah sebuah class yang konsepnya bisa kita ibaratkan sebagai orang kepo dan uptodate (gampangnya). ObserverList mendengar perubahan yang terjadi pada suatu data kemudian memberikan reaksi terhadap perubahan data tersebut. Dalam hal ini pada tabel. Dalam dunia game programming, observer juga merupakan salah satu bagian dalam design pattern service locator kalo ga salah, biasanya digunakan untuk unlock achievement sampai controller sound asset.

4. TableColumn

Instansiasi TableColumn dengan parameter constructor Judul Kolomnya

TableColumn<Pokemon, String> 
		t_col_0 = new TableColumn<Pokemon, String>("ID"), 
		t_col_1 = new TableColumn<Pokemon, String>("Nama"), 
		t_col_2 = new TableColumn<Pokemon, String>("Elemen 1"), 
		t_col_3 = new TableColumn<Pokemon, String>("Elemen 2");

Kemudian atur cellValueFactory seperti di bawah :

t_col_0.setCellValueFactory(new PropertyValueFactory<>("ID"));
t_col_1.setCellValueFactory(new PropertyValueFactory<>("nama"));
t_col_2.setCellValueFactory(new PropertyValueFactory<>("elemen_1"));
t_col_3.setCellValueFactory(new PropertyValueFactory<>("elemen_2"));

Potongan kode ‘new PropertyValueFactory<>(“ID”)’ -> “ID” disini adalah nama atribut yang direference dari data model Pokemon di atas. Getter dan setter akan dipanggil berdasarkan nama “ID” ini.

5. Setting Table

Group root  = new Group();
Scene scene = new Scene(root, 400, 450, Color.WHITE);
scene.getStylesheets().add("style.css");
t_pokemon.setLayoutX(25);
t_pokemon.setLayoutY(25);
t_pokemon.setMaxSize(350, 350);

t_col_0.setPrefWidth(50);
t_col_1.setPrefWidth(100);
t_col_2.setPrefWidth(100);
t_col_3.setPrefWidth(100);

t_pokemon.setId("my-table");

t_pokemon.setItems(t_data);
t_pokemon.getColumns().addAll(t_col_0, t_col_1, t_col_2, t_col_3);
root.getChildren().add(t_pokemon);

Tambahkan css pada scene untuk menggunakan external css. CSS yang digunakan disini hanya untuk mengatur text-align tabel agar di tengah. Berikut isi css-nya :

#my-table .table-cell
{
	-fx-alignment: CENTER;
}

6. Setting TextField dan ComboBox

TextField tf_nama = new TextField();
tf_nama.setPromptText("NAMA POKEMON");
tf_nama.setPrefWidth(100);
tf_nama.setLayoutX(25);
tf_nama.setLayoutY(400);

root.getChildren().add(tf_nama);

cb_elemen_1.setPromptText("ELEMEN 1");
cb_elemen_1.setPrefWidth(100);
cb_elemen_1.setLayoutX(135);
cb_elemen_1.setLayoutY(400);

root.getChildren().add(cb_elemen_1);

cb_elemen_2.setPromptText("ELEMEN 2");
cb_elemen_2.setPrefWidth(100);
cb_elemen_2.setLayoutX(245);
cb_elemen_2.setLayoutY(400);

root.getChildren().add(cb_elemen_2);

7. Tambah button dan listenernya

Button btn_add = new Button("+");

btn_add.setPrefWidth(10);
btn_add.setLayoutX(355);
btn_add.setLayoutY(400);

root.getChildren().add(btn_add);

btn_add.setOnAction(new EventHandler<ActionEvent>(){

	@Override
	public void handle(ActionEvent arg0) 
	{
		if(tf_nama.getText() != null && cb_elemen_1.getValue() != null && cb_elemen_2.getValue() != null)
		{
			insertTable(
				tf_nama.getText(), 
				cb_elemen_1.getSelectionModel().getSelectedIndex(), 
				cb_elemen_2.getSelectionModel().getSelectedIndex());
		}
	}
});

Prosedur insertTable akan dibuat selanjutnya.

8. Isi table sesuai data di tabel pokemon pada database


public void retrieveTable()
{
	try
	{
		Class.forName("org.h2.Driver").newInstance();
		Connection connection = 
			DriverManager.getConnection("jdbc:h2:tcp://localhost/~/saungkode", "saungkode",  "");
		
		Statement statement = connection.createStatement();
		ResultSet resultSet = 
			statement.executeQuery(
				  "SELECT poke.id_pokemon, poke.nama, elm.jenis_elemen "
				+ "FROM pokemon poke, elemen elm "
				+ "WHERE poke.elemen_primer = elm.id_elemen "
				+ "OR poke.elemen_sekunder  = elm.id_elemen");
		
		t_data.clear();
		
		String col1 = "", 
			   col2 = "", 
			   col3 = "", 
			   col4 = "";
		
		boolean odd  = true;
		int beforeID = -1;
		
		while(resultSet.next())
		{
			col1 = resultSet.getString("ID_POKEMON");
			
			if(odd)
			{
				col2 = resultSet.getString("NAMA");
				col3 = resultSet.getString("JENIS_ELEMEN");							
			}
			else
			{
				if(beforeID == Integer.parseInt(col1))
				{
					col4 = resultSet.getString("JENIS_ELEMEN");
					
					t_data.add(
						new Pokemon(col1, col2, col3, col4));
				}
				else
				{
					col4 = "-";
					
					t_data.add(
						new Pokemon(String.valueOf(beforeID), col2, col3, col4));
					
					col2 = resultSet.getString("NAMA");
					col3 = resultSet.getString("JENIS_ELEMEN");
					
					odd = !odd;
				}
			}

			beforeID = Integer.parseInt(col1);					
			odd = !odd;
		}
		
		connection.close();
		statement.close();
		resultSet.close();
	}
	catch(Exception e)
	{
		System.out.println("Error : " + e);
	}
}

9. Isi ComboBox sesuai data di tabel elemen pada database


public void retrieveComboBox()
{		
	try
	{
		Class.forName("org.h2.Driver").newInstance();
		Connection connection = 
			DriverManager.getConnection("jdbc:h2:tcp://localhost/~/saungkode", "saungkode",  "");
		
		Statement statement = connection.createStatement();
		ResultSet resultSet = 
			statement.executeQuery("SELECT JENIS_ELEMEN FROM ELEMEN");
		
		cb_elemen_1.getItems().add("-");
		cb_elemen_2.getItems().add("-");
		
		while(resultSet.next())
		{
			cb_elemen_1.getItems().add(resultSet.getString("JENIS_ELEMEN"));
			cb_elemen_2.getItems().add(resultSet.getString("JENIS_ELEMEN"));
		}
		
		connection.close();
		statement.close();
		resultSet.close();
	}
	catch(Exception e)
	{
		System.out.println("Error : " + e);
	}
}

10. Prosedur insertTable

public void insertTable(String nama, int elemen_1, int elemen_2)
{
	try
	{
		Class.forName("org.h2.Driver").newInstance();
		Connection connection = 
			DriverManager.getConnection("jdbc:h2:tcp://localhost/~/saungkode", "saungkode",  "");
		
		String query = "INSERT INTO POKEMON "
				+ "(NAMA, ELEMEN_PRIMER, ELEMEN_SEKUNDER) "
				+ "VALUES('"+nama+"', ";
		
		if(elemen_1 > 0)
		{
			query += elemen_1;
		}
		else
		{
			query += "null";
		}
		
		query += ", ";
		
		if(elemen_2 > 0)
		{
			query += elemen_2;
		}
		else
		{
			query += "null";
		}
		
		query += ")";

		
		Statement statement = connection.createStatement();
		statement.executeUpdate(query);
		
		retrieveTable();
		
		connection.close();
		statement.close();
	}
	catch(Exception e)
	{
		System.out.println("Error : " + e);
	}
}

Note : Gunakan executeUpdate pada statement untuk command Insert dan Update

DONE!!

Berikut adalah kode lengkapnya :

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class Main extends Application
{
	public static class Pokemon
	{
		private final SimpleStringProperty ID, nama, elemen_1, elemen_2;
		private Pokemon(String ID, String nama, String elemen_1, String elemen_2)
		{
			this.ID = new SimpleStringProperty(ID);
			this.nama = new SimpleStringProperty(nama);
			this.elemen_1 = new SimpleStringProperty(elemen_1);
			this.elemen_2 = new SimpleStringProperty(elemen_2);
		}
		public String getID() 
		{
			return ID.get();
		}
		public void setID(String fID)
		{
			ID.set(fID);
		}
		public String getNama() 
		{
			return nama.get();
		}
		public void setNama(String fNama)
		{
			nama.set(fNama);
		}
		public String getElemen_1() 
		{
			return elemen_1.get();
		}
		public void setElemen_1(String fElemen_1)
		{
			elemen_1.set(fElemen_1);
		}
		public String getElemen_2() 
		{
			return elemen_2.get();
		}
		public void setElemen_2(String fElemen_2)
		{
			elemen_2.set(fElemen_2);
		}
	}
	
	private final TableView<Pokemon> t_pokemon   = new TableView<>();
	private final ObservableList<Pokemon> t_data = FXCollections.observableArrayList();
	
	private final ComboBox<String> cb_elemen_1 	 = new ComboBox<String>(),
						   		   cb_elemen_2	 = new ComboBox<String>();
	
	@Override
	public void start(Stage stage) throws Exception 
	{
		Group root  = new Group();
		Scene scene = new Scene(root, 400, 450, Color.WHITE);
		scene.getStylesheets().add("style.css");
		
		TableColumn<Pokemon, String> 
			t_col_0 = new TableColumn<Pokemon, String>("ID"), 
			t_col_1 = new TableColumn<Pokemon, String>("Nama"), 
			t_col_2 = new TableColumn<Pokemon, String>("Elemen 1"), 
			t_col_3 = new TableColumn<Pokemon, String>("Elemen 2");
		
		t_col_0.setCellValueFactory(new PropertyValueFactory<>("ID"));
		t_col_1.setCellValueFactory(new PropertyValueFactory<>("nama"));
		t_col_2.setCellValueFactory(new PropertyValueFactory<>("elemen_1"));
		t_col_3.setCellValueFactory(new PropertyValueFactory<>("elemen_2"));
		
		t_pokemon.setLayoutX(25);
		t_pokemon.setLayoutY(25);
		t_pokemon.setMaxSize(350, 350);
		
		t_col_0.setPrefWidth(50);
		t_col_1.setPrefWidth(100);
		t_col_2.setPrefWidth(100);
		t_col_3.setPrefWidth(100);
		
		t_pokemon.setId("my-table");
		
		t_pokemon.setEditable(true);
		t_pokemon.setItems(t_data);
		t_pokemon.getColumns().addAll(t_col_0, t_col_1, t_col_2, t_col_3);
				
		root.getChildren().add(t_pokemon);
		
		retrieveTable();
		
		TextField tf_nama = new TextField();
		
		tf_nama.setPromptText("NAMA POKEMON");
		tf_nama.setPrefWidth(100);
		tf_nama.setLayoutX(25);
		tf_nama.setLayoutY(400);
		
		root.getChildren().add(tf_nama);
		
		cb_elemen_1.setPromptText("ELEMEN 1");
		cb_elemen_1.setPrefWidth(100);
		cb_elemen_1.setLayoutX(135);
		cb_elemen_1.setLayoutY(400);
		
		root.getChildren().add(cb_elemen_1);
		
		cb_elemen_2.setPromptText("ELEMEN 2");
		cb_elemen_2.setPrefWidth(100);
		cb_elemen_2.setLayoutX(245);
		cb_elemen_2.setLayoutY(400);
		
		root.getChildren().add(cb_elemen_2);
		
		retrieveComboBox();
		
		Button btn_add = new Button("+");
		
		btn_add.setPrefWidth(10);
		btn_add.setLayoutX(355);
		btn_add.setLayoutY(400);
		
		root.getChildren().add(btn_add);
		
		btn_add.setOnAction(new EventHandler<ActionEvent>(){

			@Override
			public void handle(ActionEvent arg0) 
			{
				if(tf_nama.getText() != null && cb_elemen_1.getValue() != null && cb_elemen_2.getValue() != null)
				{
					insertTable(
						tf_nama.getText(), 
						cb_elemen_1.getSelectionModel().getSelectedIndex(), 
						cb_elemen_2.getSelectionModel().getSelectedIndex());
				}
			}
		});
		
		stage.setScene(scene);
		stage.show();
	}
	
	public void retrieveComboBox()
	{		
		try
		{
			Class.forName("org.h2.Driver").newInstance();
			Connection connection = 
				DriverManager.getConnection("jdbc:h2:tcp://localhost/~/saungkode", "saungkode",  "");
			
			Statement statement = connection.createStatement();
			ResultSet resultSet = 
				statement.executeQuery("SELECT JENIS_ELEMEN FROM ELEMEN");
			
			cb_elemen_1.getItems().add("-");
			cb_elemen_2.getItems().add("-");
			
			while(resultSet.next())
			{
				cb_elemen_1.getItems().add(resultSet.getString("JENIS_ELEMEN"));
				cb_elemen_2.getItems().add(resultSet.getString("JENIS_ELEMEN"));
			}
			
			connection.close();
			statement.close();
			resultSet.close();
		}
		catch(Exception e)
		{
			System.out.println("Error : " + e);
		}
	}
	
	public void retrieveTable()
	{
		try
		{
			Class.forName("org.h2.Driver").newInstance();
			Connection connection = 
				DriverManager.getConnection("jdbc:h2:tcp://localhost/~/saungkode", "saungkode",  "");
			
			Statement statement = connection.createStatement();
			ResultSet resultSet = 
				statement.executeQuery(
					  "SELECT poke.id_pokemon, poke.nama, elm.jenis_elemen "
					+ "FROM pokemon poke, elemen elm "
					+ "WHERE poke.elemen_primer = elm.id_elemen "
					+ "OR poke.elemen_sekunder  = elm.id_elemen");
			
			t_data.clear();
			
			String col1 = "", 
				   col2 = "", 
				   col3 = "", 
				   col4 = "";
			
			boolean odd  = true;
			int beforeID = -1;
			
			while(resultSet.next())
			{
				col1 = resultSet.getString("ID_POKEMON");
				
				if(odd)
				{
					col2 = resultSet.getString("NAMA");
					col3 = resultSet.getString("JENIS_ELEMEN");							
				}
				else
				{
					if(beforeID == Integer.parseInt(col1))
					{
						col4 = resultSet.getString("JENIS_ELEMEN");
						
						t_data.add(
							new Pokemon(col1, col2, col3, col4));
					}
					else
					{
						col4 = "-";
						
						t_data.add(
							new Pokemon(String.valueOf(beforeID), col2, col3, col4));
						
						col2 = resultSet.getString("NAMA");
						col3 = resultSet.getString("JENIS_ELEMEN");
						
						odd = !odd;
					}
				}

				beforeID = Integer.parseInt(col1);					
				odd = !odd;
			}
			
			connection.close();
			statement.close();
			resultSet.close();
		}
		catch(Exception e)
		{
			System.out.println("Error : " + e);
		}
	}
	
	public void insertTable(String nama, int elemen_1, int elemen_2)
	{
		try
		{
			Class.forName("org.h2.Driver").newInstance();
			Connection connection = 
				DriverManager.getConnection("jdbc:h2:tcp://localhost/~/saungkode", "saungkode",  "");
			
			String query = "INSERT INTO POKEMON "
					+ "(NAMA, ELEMEN_PRIMER, ELEMEN_SEKUNDER) "
					+ "VALUES('"+nama+"', ";
			
			if(elemen_1 > 0)
			{
				query += elemen_1;
			}
			else
			{
				query += "null";
			}
			
			query += ", ";
			
			if(elemen_2 > 0)
			{
				query += elemen_2;
			}
			else
			{
				query += "null";
			}
			
			query += ")";

			
			Statement statement = connection.createStatement();
			statement.executeUpdate(query);
			
			retrieveTable();
			
			connection.close();
			statement.close();
		}
		catch(Exception e)
		{
			System.out.println("Error : " + e);
		}
	}
}

Berikan komentar anda.. :)