Cerita Pembuka: Bayangkan kita adalah seorang developer yang diminta membangun sistem informasi akademik sederhana. Seperti membangun rumah, kita perlu merencanakan arsitektur yang baik sejak awal. Tutorial ini akan mengajak Anda membangun aplikasi step-by-step dengan cara yang terstruktur dan mudah dipahami.
Setelah mengikuti tutorial ini, mahasiswa diharapkan mampu:
- Memahami dan mengimplementasikan pola MVVM (Model-View-ViewModel)
- Menerapkan DAO Pattern untuk akses database
- Membuat GUI desktop dengan JavaFX tanpa FXML
- Mengintegrasikan SQLite database dalam aplikasi Java
- Menerapkan data binding dan event handling
- Java Development Kit (JDK) 21
- IDE (IntelliJ IDEA, Eclipse, atau VS Code)
- Pemahaman dasar OOP dan Java
- Gradle (sudah terinstall dengan project)
Pertama, kita perlu memahami struktur project yang akan kita bangun:
LatAkademik/
βββ app/
β βββ build.gradle # Konfigurasi dependencies
β βββ src/main/java/latakademik/
β βββ model/ # Data models
β βββ dao/ # Data Access Objects
β βββ database/ # Database utilities
β βββ viewmodel/ # Business logic layer
β βββ view/ # User Interface
β βββ util/ # Helper utilities
β βββ AkademikApp.java # Main application
Buka file app/build.gradle dan tambahkan dependencies yang diperlukan:
plugins {
id 'application'
id 'org.openjfx.javafxplugin' version '0.1.0' // Plugin untuk JavaFX
}
dependencies {
// JavaFX untuk GUI desktop
implementation 'org.openjfx:javafx-controls:21.0.1'
implementation 'org.openjfx:javafx-fxml:21.0.1'
// SQLite untuk database
implementation 'org.xerial:sqlite-jdbc:3.44.1.0'
}
javafx {
version = '21.0.1'
modules = ['javafx.controls', 'javafx.fxml']
}π‘ Tips: JavaFX tidak lagi bundled dengan JDK sejak Java 11, jadi kita perlu menambahkannya sebagai dependency eksternal.
Dalam sistem akademik sederhana, kita memerlukan:
- Dosen: NPP, Nama, No HP
- Mahasiswa: NIM, Nama, Gender, IPK, Dosen Wali
Buat file model/Dosen.java:
package latakademik.model;
public class Dosen {
private String npp;
private String nama;
private String noHp;
// Constructor, Getters, Setters
public Dosen(String npp, String nama, String noHp) {
this.npp = npp;
this.nama = nama;
this.noHp = noHp;
}
// ... getters dan setters
@Override
public boolean equals(Object obj) {
// Implementasi berdasarkan NPP sebagai unique identifier
}
}Buat file model/Mahasiswa.java dengan atribut:
nim(String) - Primary keynama(String) - Nama lengkapgender(String) - Jenis kelaminipk(double) - IPK mahasiswadosenWali(String) - Foreign key ke NPP dosen
π Analogi: Model classes seperti blueprint rumah. Mereka mendefinisikan struktur data tanpa implementasi logic bisnis.
Buat file database/DatabaseConnection.java dengan pattern Singleton:
package latakademik.database;
public class DatabaseConnection {
private static final String DB_URL = "jdbc:sqlite:akademik.db";
private static DatabaseConnection instance;
private Connection connection;
private DatabaseConnection() {
try {
connection = DriverManager.getConnection(DB_URL);
initializeTables(); // Auto-create tables
} catch (SQLException e) {
throw new RuntimeException("Error connecting to database", e);
}
}
public static synchronized DatabaseConnection getInstance() {
if (instance == null) {
instance = new DatabaseConnection();
}
return instance;
}
private void initializeTables() {
createDosenTable();
createMahasiswaTable();
}
}SQL untuk membuat tabel:
-- Tabel Dosen
CREATE TABLE IF NOT EXISTS dosen (
npp TEXT PRIMARY KEY,
nama TEXT NOT NULL,
no_hp TEXT
);
-- Tabel Mahasiswa
CREATE TABLE IF NOT EXISTS mahasiswa (
nim TEXT PRIMARY KEY,
nama TEXT NOT NULL,
gender TEXT NOT NULL,
ipk REAL NOT NULL,
dosen_wali TEXT,
FOREIGN KEY (dosen_wali) REFERENCES dosen(npp)
);ποΈ Analogi: Database Connection seperti jembatan yang menghubungkan aplikasi dengan data storage. Singleton pattern memastikan hanya ada satu koneksi yang aktif.
DAO (Data Access Object) memisahkan logic akses data dari business logic. Ini memberikan:
- Abstraksi: Interface yang clean untuk operasi CRUD
- Fleksibilitas: Mudah mengganti implementasi database
- Testability: Mudah untuk unit testing
Buat file dao/DosenDAO.java:
package latakademik.dao;
import latakademik.model.Dosen;
import java.util.List;
public interface DosenDAO {
void save(Dosen dosen);
void update(Dosen dosen);
void delete(String npp);
Dosen findByNpp(String npp);
List<Dosen> findAll();
}Buat file dao/DosenDAOImpl.java:
package latakademik.dao;
public class DosenDAOImpl implements DosenDAO {
private final Connection connection;
public DosenDAOImpl() {
this.connection = DatabaseConnection.getInstance().getConnection();
}
@Override
public void save(Dosen dosen) {
String sql = "INSERT INTO dosen (npp, nama, no_hp) VALUES (?, ?, ?)";
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setString(1, dosen.getNpp());
stmt.setString(2, dosen.getNama());
stmt.setString(3, dosen.getNoHp());
stmt.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException("Error saving dosen", e);
}
}
// Implementasi method lainnya...
}π Best Practice: Selalu gunakan PreparedStatement untuk mencegah SQL injection dan meningkatkan performa.
MVVM memisahkan concerns menjadi tiga layer:
- Model: Data dan business rules
- View: User Interface (JavaFX components)
- ViewModel: Penghubung antara View dan Model
Buat file viewmodel/DosenViewModel.java:
package latakademik.viewmodel;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class DosenViewModel {
private final DosenDAO dosenDAO;
private final ObservableList<Dosen> dosenList;
// Properties untuk data binding
private final StringProperty npp = new SimpleStringProperty("");
private final StringProperty nama = new SimpleStringProperty("");
private final StringProperty noHp = new SimpleStringProperty("");
// Property untuk selected item
private final ObjectProperty<Dosen> selectedDosen = new SimpleObjectProperty<>();
public DosenViewModel() {
this.dosenDAO = new DosenDAOImpl();
this.dosenList = FXCollections.observableArrayList();
loadAllDosen();
}
public void saveDosen() {
if (validateInput()) {
Dosen dosen = new Dosen(npp.get(), nama.get(), noHp.get());
dosenDAO.save(dosen);
loadAllDosen();
clearForm();
}
}
// Getters untuk properties
public StringProperty nppProperty() { return npp; }
public StringProperty namaProperty() { return nama; }
// ... property getters lainnya
}JavaFX Properties memungkinkan:
- Two-way binding: Perubahan di UI otomatis update ViewModel
- Observer pattern: UI otomatis update ketika data berubah
- Validation: Mudah menambahkan validasi real-time
Buat file view/DosenView.java yang extends VBox:
package latakademik.view;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
public class DosenView extends VBox {
private final DosenViewModel viewModel;
// UI Components
private TextField nppField;
private TextField namaField;
private TableView<Dosen> dosenTable;
private Button saveButton;
public DosenView() {
this.viewModel = new DosenViewModel();
initializeComponents();
setupLayout();
bindProperties();
setupEventHandlers();
}
private void bindProperties() {
// Two-way binding antara UI dan ViewModel
nppField.textProperty().bindBidirectional(viewModel.nppProperty());
namaField.textProperty().bindBidirectional(viewModel.namaProperty());
// Bind table data
dosenTable.setItems(viewModel.getDosenList());
}
private void setupEventHandlers() {
saveButton.setOnAction(e -> {
viewModel.saveDosen();
showSuccessMessage("Data berhasil disimpan!");
});
}
}private void setupTable() {
dosenTable = new TableView<>();
// Kolom NPP
TableColumn<Dosen, String> nppColumn = new TableColumn<>("NPP");
nppColumn.setCellValueFactory(new PropertyValueFactory<>("npp"));
// Kolom Nama
TableColumn<Dosen, String> namaColumn = new TableColumn<>("Nama");
namaColumn.setCellValueFactory(new PropertyValueFactory<>("nama"));
dosenTable.getColumns().addAll(nppColumn, namaColumn);
}Buat file AkademikApp.java:
package latakademik;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TabPane;
import javafx.stage.Stage;
public class AkademikApp extends Application {
@Override
public void start(Stage primaryStage) {
// Initialize database
DatabaseConnection.getInstance();
// Create main layout dengan tabs
Scene mainScene = createMainScene();
Scene scene = new Scene(mainScene, 1000, 700);
primaryStage.setTitle("Sistem Akademik");
primaryStage.setScene(scene);
primaryStage.show();
}
private Scene createMainScene() {
BorderPane root = new BorderPane();
// Create header
VBox header = createHeader();
root.setTop(header);
// Create main content with tabs
mainTabPane = createMainTabs();
root.setCenter(mainTabPane);
// Create footer
HBox footer = createFooter();
root.setBottom(footer);
return new Scene(root);
}
private VBox createHeader() {
VBox header = new VBox();
header.getStyleClass().add("header");
header.setPadding(new Insets(20));
Label titleLabel = new Label("π Sistem Informasi Akademik");
titleLabel.getStyleClass().add("main-title");
Label subtitleLabel = new Label("Manajemen Data Mahasiswa dan Dosen");
subtitleLabel.getStyleClass().add("subtitle");
header.getChildren().addAll(titleLabel, subtitleLabel);
return header;
}
private TabPane createMainTabs() {
TabPane tabPane = new TabPane();
tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
// Tab Data Mahasiswa
mahasiswaView = new MahasiswaView();
Tab mahasiswaTab = new Tab("π¨βπ Data Mahasiswa", mahasiswaView);
// Tab Data Dosen
dosenView = new DosenView();
Tab dosenTab = new Tab("π¨βπ« Data Dosen", dosenView);
tabPane.getTabs().addAll(mahasiswaTab, dosenTab);
return tabPane;
}
private HBox createFooter() {
HBox footer = new HBox();
footer.getStyleClass().add("footer");
footer.setPadding(new Insets(10));
Label footerLabel = new Label("Β© 2024 - Sistem Akademik | Dibuat dengan β€οΈ menggunakan JavaFX");
footerLabel.getStyleClass().add("footer-text");
footer.getChildren().add(footerLabel);
return footer;
}
}Tambahkan method untuk styling di AkademikApp.java:
private String createInlineCSS() {
return "data:text/css," + """
.root {
-fx-font-family: 'Segoe UI', sans-serif;
-fx-background-color: #f5f5f5;
}
.button-primary {
-fx-background-color: #667eea;
-fx-text-fill: white;
-fx-font-weight: bold;
}
.button-primary:hover {
-fx-background-color: #5a6fd8;
}
""";
}// Auto-resize columns
dosenTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
// Responsive button layout
HBox buttonBox = new HBox(10);
buttonBox.getChildren().addAll(saveButton, updateButton, deleteButton);Implementasi validasi di ViewModel:
private boolean validateInput() {
if (npp.get().trim().isEmpty()) {
showErrorMessage("NPP tidak boleh kosong!");
return false;
}
if (nama.get().trim().isEmpty()) {
showErrorMessage("Nama tidak boleh kosong!");
return false;
}
return true;
}public void saveDosen() {
try {
if (validateInput()) {
Dosen dosen = new Dosen(npp.get(), nama.get(), noHp.get());
dosenDAO.save(dosen);
loadAllDosen();
clearForm();
showSuccessMessage("Data berhasil disimpan!");
}
} catch (Exception e) {
showErrorMessage("Error: " + e.getMessage());
}
}Buat file util/SampleDataUtil.java:
public class SampleDataUtil {
public void insertSampleData() {
// Insert sample dosen
Dosen[] sampleDosen = {
new Dosen("NPP001", "Dr. Ahmad Fauzi", "08123456789"),
new Dosen("NPP002", "Dr. Siti Rahayu", "08234567890")
};
for (Dosen dosen : sampleDosen) {
dosenDAO.save(dosen);
}
}
}# Compile dan run dengan Gradle
./gradlew run
# Atau build JAR file
./gradlew build- Tutup database connection saat aplikasi ditutup
- Gunakan try-with-resources untuk resource management
- Avoid memory leaks dengan proper event listener cleanup
- Single Responsibility: Setiap class punya satu tanggung jawab
- Dependency Injection: Inject dependencies melalui constructor
- Error Handling: Consistent error handling strategy
- Use ObservableList untuk table data
- Implement lazy loading untuk data besar
- Cache frequently accessed data
- Search Functionality: Tambahkan TextField untuk search
- Export Data: Export table data ke CSV/Excel
- Data Import: Import data dari file eksternal
- User Authentication: Tambahkan login system
- Migration Scripts: Untuk update schema
- Backup/Restore: Fitur backup database
- Connection Pooling: Untuk aplikasi yang lebih besar
- Arsitektur Aplikasi: MVVM dan DAO patterns
- JavaFX Programming: GUI tanpa FXML
- Database Integration: SQLite dengan JDBC
- Data Binding: JavaFX Properties dan ObservableList
- Best Practices: Error handling, validation, styling
- Separation of Concerns adalah fundamental dalam aplikasi yang maintainable
- Data Binding membuat UI yang responsive dan reactive
- Pattern Implementation memudahkan testing dan extensibility
- User Experience sama pentingnya dengan functionality
- Explore advanced JavaFX features (Charts, Animations)
- Learn unit testing dengan JUnit
- Implement more design patterns (Observer, Factory)
- Study enterprise patterns (Repository, Service Layer)
Penutup: Seperti membangun rumah, membangun aplikasi memerlukan perencanaan yang matang dan eksekusi yang teliti. Dengan mengikuti tutorial ini, Anda telah belajar membangun aplikasi desktop Java yang terstruktur, maintainable, dan user-friendly. Selamat coding! π
Happy Coding! π»β¨