Reputation: 57
I'm new to JavaFX and I was wondering what are the best practices in this language to develop a MVC database application, I think my question will be pretty simple if you are an senior developer.
Let us consider a simple example of a basic application developed in JavaFX : a ToDoList linked with a SQL Database.
That's what our application looks like :
I decided to split my code into four parts, DAO for the classes who represents datas in the table (Task.java), the DAO class who access the database (its behavior does not matter here). The model who represents the Model part of our TodoList (containing a list of task and performing operations on it, calling the DAO, etc..). The FXML Views and the Controller :
Next, you can find the code of the different classes that interest us (We supposed that the DAO is OK (setting id automatically) an we do not handle error cases to simplify code :
Task.java
public class Task {
private int id;
private SimpleStringProperty task;
public Task(int i, String s){
this.id = i;
this.task = new SimpleStringProperty(s);
}
public void setId(int i){
this.id = i;
}
public int getId() {
return id;
}
public String getTask() {
return task.get();
}
public void setTask(String task) {
this.task.set(task);
}
@Override
public boolean equals(Object o){
if(this.id == ((Task)o).id)
return true;
return false;
}
}
ToDoListModel.java
public class ToDoListModel {
private List<Task> taskList;
private DAO dao;
public ToDoListModel(){
this.taskList = new ArrayList<Task>();
this.dao = new DAO();
}
public void loadDatabase(){
this.taskList = this.dao.getAllTasks();
}
public void addTask(Task t){
// Operations throwing Exceptions such as : Does the task t is already in the list, etc...
this.taskList.add(t);
this.dao.createTask(t);
}
public void deleteTask(Task t){
this.taskList.remove(t);
this.dao.deleteTask(t);
}
public List<Task> getTaskList() {
return taskList;
}
}
Controller.java
public class Controller {
private final ToDoListModel model;
@FXML
private TableView<Task> taskTable;
@FXML
private TableColumn<Task, String> taskColumn;
@FXML
private TextField taskTextField;
public Controller(ToDoListModel m){
this.model = m;
}
@FXML
protected void initialize() {
this.model.loadDatabase();
// Setting up data table
taskColumn.setCellValueFactory(new PropertyValueFactory<Task, String>("task"));
ObservableList<Task> taskObservableList = FXCollections.observableList(this.model.getTaskList());
taskTable.setItems(taskObservableList);
}
@FXML
public void handleAddButton(ActionEvent e) {
Task t = new Task(-1, this.taskTextField.getText());
// What operations to do here ?
this.model.addTask(t);
this.taskTable.getItems().add(t);
this.taskTable.refresh();
}
}
Main.java
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception{
ToDoListModel model = new ToDoListModel();
primaryStage.setTitle("My Todo");
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("views/View.fxml"));
loader.setController(new Controller(model));
Parent root = loader.load();
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Finally, my question is : Is my approach good ? I mean the fact that I've created a ToDoListModel with a list of task, the fact that I update my list of Objects Task at the same task I update my database with the DAO (a create in the DAO will be performed after an add in the list) and the most important : what operations should I do in the handleAddButton of my Controller ? Here I used first the add method in my TodoListModel but it's not enough because my observable list is wrongly updated (The added task appears but we can not select it with the mouse). Then, when I add it also in the TableView items, the Task appears twice and has been added twice in the list.
As a result I've understood that the ObservableList was linked to the List I have in my ToDoListModel but what am I supposed to do if I want to do operations on that list only in my model but getting the ObservableList updated correctly ? (Selectable item etc...)
Thank you in advance for your help and your patience, Sincerely, Paul
Upvotes: 4
Views: 9055
Reputation: 2859
Here is an example implementation
The DAO
class takes care of connecting to the database (may use a pool or something else). In this case, it makes a simple connection.
public class DAO {
public Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:mysql://192.168.40.5:3306/test", "root", "");
}
}
The ToDoListModel
class takes care of working with the database by using an instance of DAO
to get a valid connection.
public class ToDoListModel {
private DAO dao;
public static ToDoListModel getInstance() {
ToDoListModel model = new ToDoListModel();
model.dao = new DAO();
return model;
}
private ToDoListModel() {
}
public void addTask(Task task) throws SQLException {
try(Connection connection = dao.getConnection()) {
String q = "insert into todo (name) values (?)";
try(PreparedStatement statement = connection.prepareStatement(q, Statement.RETURN_GENERATED_KEYS)) {
statement.setString(1, task.getName());
statement.executeUpdate();
try(ResultSet rs = statement.getGeneratedKeys()) {
if(rs.next()) {
task.setId(rs.getInt(1));
}
}
}
}
}
public void deleteTask(Task task) throws SQLException {
try(Connection connection = dao.getConnection()) {
String q = "delete from todo where id = ?";
try(PreparedStatement statement = connection.prepareStatement(q)) {
statement.setInt(1, task.getId());
statement.executeUpdate();
}
}
}
public ObservableList<Task> getTaskList() throws SQLException {
try(Connection connection = dao.getConnection()) {
String q = "select * from todo";
try(Statement statement = connection.createStatement()) {
try(ResultSet rs = statement.executeQuery(q)) {
ObservableList<Task> tasks = FXCollections.observableArrayList();
while (rs.next()) {
Task task = new Task();
task.setId(rs.getInt("id"));
task.setName(rs.getString("name"));
tasks.add(task);
}
return tasks;
}
}
}
}
}
The controller uses ToDoListModel
to initialize TableView
controls and add operations (editing and reading - I did not implement them because I stick to your code)
public class Controller {
@FXML
private TextField textField;
@FXML
private TableView<Task> tableView;
@FXML
private TableColumn<Task, String> nameTableColumn;
@FXML
private Button addButton;
@FXML
private void initialize() {
nameTableColumn.setCellValueFactory(cdf -> cdf.getValue().nameProperty());
addButton.disableProperty().bind(Bindings.isEmpty(textField.textProperty()));
CompletableFuture.supplyAsync(this::loadAll)
.thenAccept(list -> Platform.runLater(() -> tableView.getItems().setAll(list)))
.exceptionally(this::errorHandle);
}
@FXML
private void handleAddButton(ActionEvent event) {
CompletableFuture.supplyAsync(this::addTask)
.thenAccept(task -> Platform.runLater(() -> {
tableView.getItems().add(task);
textField.clear();
textField.requestFocus();
}))
.exceptionally(this::errorHandle);
}
private Task addTask() {
try {
Task task = new Task(textField.getText());
ToDoListModel.getInstance().addTask(task);
return task;
}
catch (SQLException e) {
throw new RuntimeException(e);
}
}
private ObservableList<Task> loadAll() {
try {
return ToDoListModel.getInstance().getTaskList();
}
catch (SQLException e) {
throw new RuntimeException(e);
}
}
private Void errorHandle(Throwable throwable) {
throwable.printStackTrace();
return null;
}
}
Any database operations are asynchronous with CompletableFuture
but you can use whatever you prefer. The important thing is to remember that UI threads can only be made uniquely by it.
Upvotes: 3