Sunteți pe pagina 1din 23

APLICAȚII JAVA FXML CU BAZE DE DATE

Breviar teoretic

În industrie trendul aplicațiilor grafice cu interfața definită printr-o variantă a limbajului XML (Extensible Markup
Language) este în continuă creștere. Pentru a satisface aceste cerințe Microsoft au inventat XAML, Flex – MXML, iar
acum și Oracle are o variantă proprietară numită Java FXML.
Java FXML este un limbaj bazat pe XML folosit la construirea de grafuri de obiecte Java. Este o alternativă la
construirea acestor grafuri prin metode de cod procedural și este ideal pentru definirea de interfețe cu utilizatorul,
deoarece structura unui document XML este foarte asemănătoare cu structura de cod folosită la definirea unui graf
de scenă într-o aplicație Java FX obișnuită.

FXML nu are o schemă, dar are o structură predefinită. Ceea ce poate fi exprimat în FXML și cum este aplicat grafului
scenei depinde de interfața de cod a obiectelor construite. Deoarece FXML este mapat direct în cod Java, aproape
toate clasele de Java FX au un element XML corespondent cu atributele descrise în clasa Java mapată.

Java FX

BorderPane border = new BorderPane();

Label topPaneText = new Label("Page Title");

border.setTop(topPaneText);

Label centerPaneText = new Label ("Some data here");

border.setCenter(centerPaneText);

Java FXML

<BorderPane>

<top>

<Label text="Page Title"/>

</top>

<center>

<Label text="Some data here"/>

</center>

</BorderPane>
Pentru a eficientiza procesul de dezvoltare al aplicațiilor cu interfețe grafice au fost create șabloane (patternuri) și
arhitecturi de aplicații. Cel mai popular pattern pentru aplicațiile desktop este Model View Controller, care a devenit
folosit chiar și în aplicațiile web.

Patternul Model View Controller este o metodă de a implementa interfețe cu utilizatorul, care divide software-ul
implementat în trei părți interconectate, pentru a separa reprezentările interne de informațiile prezentate
utilizatorilor.

Model Punctul central al aplicației: datele și regulile procesului. Este


independent de interfață.

View Afișează datele din model. Are și rolul de a transmite evenimente


legate de interacțiunea utilizatorului la Controller.

Controlle Conectează modelul si view-ul (interfața cu utilizatorul). Are rolul


r de a pregăti datele pentru afișare și de a răspunde la comenzile
primite din interfață.

Din perspectiva patternului Model View Controller, fișierul care conține descrierea XML a interfeței este View-ul.
Controllerul este o clasă de Java care este declarată ca și controllerul fișierului XML. Modelul este constituit din
obiectele domeniului definite în Java. Modelul se leagă de interfață folosind Controllere.

Pentru a interacționa cu o bază de date vom încapsula logica necesară interogărilor și extragerii primare de date
folosind un alt pattern numit Data Access Object. Această abstractizare permite operațiuni specifice (de exemplu
operațiile CRUD) fără a divulga detaliile mecanismului de persistență a datelor.

Aceste două patternuri ne vor ajuta să ne structurăm aplicația într-o manieră scalabilă și ușor de înțeles.
Aplicație
Realizarea unei aplicații grafice folosind Java FXML și conectarea acesteia la o bază
de date SQLite

Aplicația pe care o vom dezvolta se va conecta la o bază de date SQLite, care conține o tabelă Person
(FirstName, LastName), și va pune la dispoziția utilizatorului o interfața prietenoasă prin care se poate realiza
operațiile de CRUD (Create, Read, Update, Delete). Interfața va arăta în felul următor:

Vom folosi mediul de programare NetBeans și vom crea un nou proiect Java – Application cu numele
SqliteJavaFxml. Pentru aplicația noastră avem nevoie de pachetul sqlite-jdbc, pe care îl putem downloada de la
acest link sqlite-jdbc-3.8.11.2.jar.
După ce am descărcat sqlite-jdbc, trebuie adăugat la referințele proiectului in directorul Libraries – click dreapta →
Add JAR/Folder → Alegem fișierul nou descărcat sqlite-jdbc-3.8.11.2.jar.

Următorii pași care sunt sugerați în dezvoltarea acestei aplicații sunt stabilirea structurii de fișiere. Ne vom crea
module (packages) astfel încât să urmăm principiile MVC și DAO.

Vom redenumi packageul implicit sqlite.java.fxml în model pentru a respecta convenția noastră.

Apoi vom crea și următoarele module view și dataaccess. Restul fișierelor vor fi adăugate în modulele aferente
folosind meniul contextual click dreapta pe modul și Add Other…

Structura finală a proiectului va fi cea din figura alăturată:


Vom porni de la stabilirea clară a modelului aplicației, adică fișierul Person.java, urmat de layerul de acces la baza de
date (PersonDAO – Data Access Object), clasa principală a aplicației noastre (SqliteJavaFxml.java) care face legatura
dintre toate componentele, la urmă rămânând fișierele care descriu interfața și controllerele asociate acestora.

Fișierul Person.java conține definiția clasei Person, care va corespunde ca și proprietăți tabelei Person din baza de
date folosită în aplicație.

package model;

import javafx.beans.property.SimpleStringProperty;

import javafx.beans.property.StringProperty;

public class Person {

private int id;

private final StringProperty firstName;

private final StringProperty lastName;

public Person() {

this(0, null, null);

public Person(int id, String firstName, String lastName) {

this.id = id;

this.firstName = new SimpleStringProperty(firstName);

this.lastName = new SimpleStringProperty(lastName);

public int getId() {

return id;

public void setId(int id) {

this.id = id;
}

public String getFirstName() {

return firstName.get();

public void setFirstName(String firstName) {

this.firstName.set(firstName);

public StringProperty firstNameProperty() {

return firstName;

public String getLastName() {

return lastName.get();

public void setLastName(String lastName) {

this.lastName.set(lastName);

public StringProperty lastNameProperty() {

return lastName;

După definirea proprietăților, vom putea scrie mai departe modulul de acces la baza de date. Acest modul se va
ocupa de crearea tabelei în cazul în care nu există deja și de interacționarea din cod Java cu tabela Person (Create,
Read, Update, Delete). Conținutul fișierului PersonDAO va fi următorul:
package dataaccess;

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.sql.Statement;

import java.util.ArrayList;

import java.util.List;

import java.util.logging.Level;

import java.util.logging.Logger;

import model.SqliteJavaFxml;

import model.Person;

public class PersonDAO {

private final String databaseConnectionString;

public PersonDAO(String databaseConnectionString) throws ClassNotFoundException {

Class.forName("org.sqlite.JDBC");

this.databaseConnectionString = databaseConnectionString;

this.ensureTables();

private void ensureTables() {

try (Connection connection = DriverManager.getConnection(databaseConnectionString);

Statement statement = connection.createStatement()) {

String query = "CREATE TABLE IF NOT EXISTS PERSON("

+ "ID INTEGER PRIMARY KEY AUTOINCREMENT,"


+ "FIRST_NAME NVARCHAR(32),"

+ "LAST_NAME NVARCHAR(32));";

statement.execute(query);

System.out.println("Tables created successfully!");

} catch (SQLException ex) {

Logger.getLogger(SqliteJavaFxml.class.getName()).log(Level.SEVERE, null, ex);

public List<Person> getAllPersons() {

List<Person> persons = new ArrayList<Person>();

try (Connection connection = DriverManager.getConnection(databaseConnectionString);

Statement statement = connection.createStatement()) {

String query = "SELECT ID, FIRST_NAME, LAST_NAME FROM PERSON;";

try (ResultSet rs = statement.executeQuery(query)) {

while (rs.next()) {

int id = rs.getInt("ID");

String firstName = rs.getString("FIRST_NAME");

String lastName = rs.getString("LAST_NAME");

persons.add(new Person(id, firstName, lastName));

} catch (SQLException ex) {

Logger.getLogger(SqliteJavaFxml.class.getName()).log(Level.SEVERE, null, ex);

return persons;

}
public Person getPerson(int personId) {

Person person = null;

try (Connection connection = DriverManager.getConnection(databaseConnectionString);

PreparedStatement statement

= connection.prepareStatement("SELECT ID, FIRST_NAME, LAST_NAME FROM PERSON


WHERE ID = ?;")) {

statement.setInt(1, personId);

try (ResultSet rs = statement.executeQuery()) {

while (rs.next()) {

int id = rs.getInt("ID");

String firstName = rs.getString("FIRST_NAME");

String lastName = rs.getString("LAST_NAME");

person = new Person(id, firstName, lastName);

} catch (SQLException ex) {

Logger.getLogger(SqliteJavaFxml.class.getName()).log(Level.SEVERE, null, ex);

return person;

public boolean insertPerson(Person person) {

boolean success = true;

try (Connection connection = DriverManager.getConnection(databaseConnectionString);

PreparedStatement statement

= connection.prepareStatement("INSERT INTO PERSON (FIRST_NAME, LAST_NAME)


VALUES (?, ?);")) {

statement.setString(1, person.getFirstName());

statement.setString(2, person.getLastName());
statement.executeUpdate();

} catch (SQLException ex) {

Logger.getLogger(SqliteJavaFxml.class.getName()).log(Level.SEVERE, null, ex);

success = false;

return success;

public boolean updatePerson(Person person) {

boolean success = true;

try (Connection connection = DriverManager.getConnection(databaseConnectionString);

PreparedStatement statement

= connection.prepareStatement("UPDATE PERSON "

+ " SET FIRST_NAME = ?, LAST_NAME = ? "

+ " WHERE ID = ?;")) {

statement.setString(1, person.getFirstName());

statement.setString(2, person.getLastName());

statement.setInt(3, person.getId());

statement.executeUpdate();

} catch (SQLException ex) {

Logger.getLogger(SqliteJavaFxml.class.getName()).log(Level.SEVERE, null, ex);

success = false;

return success;

public boolean deletePerson(Person person) {

boolean success = true;

try (Connection connection = DriverManager.getConnection(databaseConnectionString);

PreparedStatement statement
= connection.prepareStatement("DELETE FROM PERSON WHERE ID = ?;")) {

statement.setInt(1, person.getId());

statement.executeUpdate();

} catch (SQLException ex) {

Logger.getLogger(SqliteJavaFxml.class.getName()).log(Level.SEVERE, null, ex);

success = false;

return success;

Fișierul principal al aplicației noastre îl constituie SqliteJavaFxml.java și va conține codul pentru gestionarea scenei și
a viewurilor. În acest fișier vom scrie următoarele:

package model;

import dataaccess.PersonDAO;

import java.io.IOException;

import javafx.application.Application;

import javafx.fxml.FXMLLoader;

import javafx.scene.Scene;

import javafx.scene.layout.AnchorPane;

import javafx.scene.layout.BorderPane;

import javafx.stage.Modality;

import javafx.stage.Stage;

import view.PersonEditDialogController;

import view.PersonOverviewController;

public class SqliteJavaFxml extends Application {


private Stage primaryStage;

private BorderPane rootLayout;

private final PersonDAO personDAO;

public SqliteJavaFxml() throws ClassNotFoundException {

this.personDAO = new PersonDAO("jdbc:sqlite:crudapp.db");

public PersonDAO getPersonDAO() {

return this.personDAO;

@Override

public void start(Stage primaryStage) {

this.primaryStage = primaryStage;

this.primaryStage.setTitle("SQLite Java FXML Crud App");

initRootLayout();

showPersonOverview();

public void initRootLayout() {

try {

FXMLLoader loader = new FXMLLoader();

loader.setLocation(SqliteJavaFxml.class.getResource("/view/RootLayout.fxml"));

rootLayout = (BorderPane) loader.load();

Scene scene = new Scene(rootLayout);


primaryStage.setScene(scene);

primaryStage.show();

} catch (IOException e) {

public void showPersonOverview() {

try {

FXMLLoader loader = new FXMLLoader();

loader.setLocation(SqliteJavaFxml.class.getResource("/view/PersonOverview.fxml"));

AnchorPane personOverview = (AnchorPane) loader.load();

rootLayout.setCenter(personOverview);

PersonOverviewController controller = loader.getController();

controller.setMainApp(this);

} catch (IOException e) {

public boolean showPersonEditDialog(Person person) {

try {

FXMLLoader loader = new FXMLLoader();

loader.setLocation(SqliteJavaFxml.class.getResource("/view/PersonEditDialog.fxml"));

AnchorPane page = (AnchorPane) loader.load();

Stage dialogStage = new Stage();

dialogStage.setTitle("Edit Person");

dialogStage.initModality(Modality.WINDOW_MODAL);
dialogStage.initOwner(primaryStage);

Scene scene = new Scene(page);

dialogStage.setScene(scene);

PersonEditDialogController controller = loader.getController();

controller.setDialogStage(dialogStage);

controller.setPerson(person);

dialogStage.showAndWait();

return controller.isOkClicked();

} catch (IOException e) {

return false;

public Stage getPrimaryStage() {

return primaryStage;

public static void main(String[] args) {

launch(args);

Observăm că în constructorul clasei principale se află instanțierea Data Access Objectului pentru persoane care
primește ca parametru adresa bazei de date SQLite. Reamintim că această baze de date se creează automat la
rularea programului direct în directorul proiectului.

this.personDAO = new PersonDAO("jdbc:sqlite:crudapp.db");


Metodele care controlează viewurile sunt initRootLayout, showPersonOverview, showPersonEditDialog.
După cum se poate observa, toate cele trei metode au o structura similară, în sensul că se deschid cu un bloc de
gestionare a excepțiilor provenite din lipsa fișierelor (Input/Output) sau lipsa accesului la aceste resurse. Resursele,
adică fișierele în care sunt descrise viewurile sunt încărcate cu ajutorul clasei FXMLLoader, folosind metodele
setLocation si load.

Pentru a lega controllerele de aceste viewuri, apelăm tot la loaderul FXML, folosind metoda getController.

Vom continua cu definirea fiecărui View și Controller asociat.

Fișierul RootLayout.fxml va conține doar un cadru pentru aplicația noastră, va fi practic containerul care
dimensionează fereastra principală a aplicației.

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>

<?import java.lang.*?>

<?import javafx.scene.layout.*?>

<?import javafx.scene.layout.BorderPane?>

<BorderPane prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1"


xmlns="http://javafx.com/javafx/8.0.40">

</BorderPane>

Acest View nu are nevoie de un controller asociat deoarece nu are legături de date, el fiind folosit doar pentru a
aranja layoutul aplicației.

Fișierul PersonOverview.fxml va conține descrierea tabelului FXML corespunzător tabelei Person dintr-o bază de
date SQLite, care va fi folosit desigur la extragerea și afișarea datelor din acea tabelă. În acest View vom regăsi și trei
butoane de acțiune care au rolul de a manipula datele din tabel, respectiv baza de date, prin intermediul de ferestre
intermediare sau acțiuni asupra modelului.

De remarcat sunt legarea proprietăților din controller la view cu atributul fx:id (fx:id="firstNameColumn") și
legarea funcțiilor cu prefixul "#" (#handleNewPerson);

<?xml version="1.0" encoding="UTF-8"?>


<?import javafx.scene.control.*?>

<?import java.lang.*?>

<?import javafx.scene.layout.*?>

<?import javafx.scene.layout.AnchorPane?>

<AnchorPane prefHeight="300.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40"


xmlns:fx="http://javafx.com/fxml/1" fx:controller="view.PersonOverviewController">

<children>

<TableView fx:id="personTable" layoutX="-12.0" layoutY="49.0" prefHeight="298.0"


prefWidth="175.0" AnchorPane.bottomAnchor="45.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">

<columns>

<TableColumn fx:id="firstNameColumn" prefWidth="75.0" text="First Name" />

<TableColumn fx:id="lastNameColumn" prefWidth="75.0" text="Last Name" />

</columns>

<columnResizePolicy>

<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />

</columnResizePolicy>

</TableView>

<ButtonBar layoutX="54.0" layoutY="250.0" AnchorPane.bottomAnchor="10.0"


AnchorPane.rightAnchor="10.0">

<buttons>

<Button mnemonicParsing="false" onAction="#handleNewPerson" text="New..." />

<Button mnemonicParsing="false" onAction="#handleEditPerson"


text="Edit..." />

<Button mnemonicParsing="false" onAction="#handleDeletePerson" text="Delete"


/>

</buttons>

</ButtonBar>

</children>

</AnchorPane>
Controllerul acestui View se regăsește în fișierul PersonOverviewController.java și va face legătura dintre interfața
grafica și model, persistând rezultatul acțiunilor utilizatorului în baza de date prin intermediul Person Data Access
Objectului.

De remarcat sunt decoratorii (@FXML) folosiți pentru a marca proprietățile și funcțiile ce interacționează în corpul lor
cu elemente din fișierul FXML pe care îl controlează. Dacă metodele, respectiv proprietățile nu sunt decorate cu
@FXML, aplicația va avea erori de funcționare. În cazul în care nu este selectată nicio înregistrare din tabel, apăsarea
butoanelor de acțiune pentru editare și ștergere vor declanșa apariția unui mesaj de eroare (metoda showAlert).

package view;

import dataaccess.PersonDAO;

import java.util.List;

import javafx.collections.FXCollections;

import javafx.collections.ObservableList;

import javafx.fxml.FXML;

import javafx.scene.control.Alert;

import javafx.scene.control.Alert.AlertType;

import javafx.scene.control.TableColumn;

import javafx.scene.control.TableView;

import model.SqliteJavaFxml;

import model.Person;

public class PersonOverviewController {

@FXML

private TableView<Person> personTable;

@FXML

private TableColumn<Person, String> firstNameColumn;

@FXML

private TableColumn<Person, String> lastNameColumn;

private SqliteJavaFxml mainApp;


private PersonDAO personDAO;

public PersonOverviewController() {

@FXML

private void initialize() {

firstNameColumn.setCellValueFactory(cellData ->
cellData.getValue().firstNameProperty());

lastNameColumn.setCellValueFactory(cellData ->
cellData.getValue().lastNameProperty());

public void setMainApp(SqliteJavaFxml mainApp) {

this.mainApp = mainApp;

handleRefreshPersons();

@FXML

private void handleDeletePerson() {

Person selectedPerson = personTable.getSelectionModel().getSelectedItem();

if (selectedPerson != null) {

mainApp.getPersonDAO().deletePerson(selectedPerson);

handleRefreshPersons();

} else {

showAlert();

@FXML

private void handleNewPerson() {

Person tempPerson = new Person();


boolean okClicked = mainApp.showPersonEditDialog(tempPerson);

if (okClicked) {

mainApp.getPersonDAO().insertPerson(tempPerson);

handleRefreshPersons();

@FXML

private void handleEditPerson() {

Person selectedPerson = personTable.getSelectionModel().getSelectedItem();

if (selectedPerson != null) {

boolean okClicked = mainApp.showPersonEditDialog(selectedPerson);

if (okClicked) {

mainApp.getPersonDAO().updatePerson(selectedPerson);

handleRefreshPersons();

} else {

showAlert();

@FXML

private void handleRefreshPersons() {

List<Person> persons = mainApp.getPersonDAO().getAllPersons();

ObservableList<Person> items = FXCollections.observableArrayList(persons);

personTable.setItems(items);

private void showAlert() {

Alert alert = new Alert(AlertType.WARNING);

alert.initOwner(mainApp.getPrimaryStage());
alert.setTitle("No Selection");

alert.setHeaderText("No Person Selected");

alert.setContentText("Please select a person in the table.");

alert.showAndWait();

Ultimul view folosit în aplicație este cel care descrie fereastra de tip dialog prin care un utilizator poate introduce
datele unei persoane noi sau poate edita pe cele ale uneia existente în baza de date. În funcție de metoda de unde a
fost apelat controllerul acestui view, rezultatul acțiunii de a apăsa butonul OK va declanșa o acțiune de adăugare sau
de editare a tabelei Person.

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>

<?import java.lang.*?>

<?import javafx.scene.layout.*?>

<?import javafx.scene.layout.AnchorPane?>

<AnchorPane prefHeight="120.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/8.0.40"


xmlns:fx="http://javafx.com/fxml/1" fx:controller="view.PersonEditDialogController">

<children>

<GridPane layoutX="30.0" layoutY="28.0" AnchorPane.leftAnchor="10.0"


AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0">

<columnConstraints>

<ColumnConstraints prefWidth="150.0" />

<ColumnConstraints prefWidth="250.0" />

</columnConstraints>

<rowConstraints>

<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />

<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />


</rowConstraints>

<children>

<Label text="First Name" />

<Label text="Last Name" GridPane.rowIndex="1" />

<TextField fx:id="firstNameField" GridPane.columnIndex="1" />

<TextField fx:id="lastNameField" GridPane.columnIndex="1"


GridPane.rowIndex="1" />

</children>

</GridPane>

<ButtonBar AnchorPane.bottomAnchor="10.0" AnchorPane.rightAnchor="10.0">

<buttons>

<Button mnemonicParsing="false" onAction="#handleOk" text="OK" />

<Button mnemonicParsing="false" onAction="#handleCancel" text="Cancel" />

</buttons>

</ButtonBar>

</children>

</AnchorPane>

Controllerul pentru acest view, PersonEditDialogController.java , este folosit pentru a verifica introducerea corectă
de date și validarea modificarărilor la nivel de interfață. Acesta comunică schimbările modelului controllerului
PersonOverviewController, iar acesta din urmă va acționa și în baza de date.

package view;

import javafx.fxml.FXML;

import javafx.scene.control.Alert;

import javafx.scene.control.Alert.AlertType;

import javafx.scene.control.TextField;

import javafx.stage.Stage;

import model.Person;

public class PersonEditDialogController {


@FXML

private TextField firstNameField;

@FXML

private TextField lastNameField;

private Stage dialogStage;

private Person person;

private boolean okClicked = false;

@FXML

private void initialize() {

public void setDialogStage(Stage dialogStage) {

this.dialogStage = dialogStage;

public void setPerson(Person person) {

this.person = person;

firstNameField.setText(person.getFirstName());

lastNameField.setText(person.getLastName());

public boolean isOkClicked() {

return okClicked;

@FXML
private void handleOk() {

if (isInputValid()) {

person.setFirstName(firstNameField.getText());

person.setLastName(lastNameField.getText());

okClicked = true;

dialogStage.close();

@FXML

private void handleCancel() {

dialogStage.close();

private boolean isInputValid() {

String errorMessage = "";

if (firstNameField.getText() == null || firstNameField.getText().length() == 0) {

errorMessage += "No valid first name!\n";

if (lastNameField.getText() == null || lastNameField.getText().length() == 0) {

errorMessage += "No valid last name!\n";

if (errorMessage.length() == 0) {

return true;

} else {

Alert alert = new Alert(AlertType.ERROR);

alert.initOwner(dialogStage);

alert.setTitle("Invalid Fields");
alert.setHeaderText("Please correct invalid fields");

alert.setContentText(errorMessage);

alert.showAndWait();

return false;

Putem vedea relația dintre aceste doua controllere ca Master-Slave, unul fiind utilizat pentru controlul la nivel doar
de interfață (PersonEditDialogController), iar celălalt pentru a gestiona logica aplicației.

Așadar, am realizat o aplicație cu interfață grafică, susținută de o bază de date SQLite. Concluzia este că aplicând o
arhitectură MVC putem scrie codul într-un mod modular, ușor de extins, care ține cont de patternuri aplicate în
tehnicile moderne de programare și putem îl refolosi pentru aplicații asemănătoare.

S-ar putea să vă placă și