JavaFX - лучшие практики применения MVC с базой данных

Я новичок в JavaFX, и мне было интересно, каковы лучшие практики на этом языке для разработки приложения базы данных MVC, я думаю, мой вопрос будет довольно простым, если вы старший разработчик.

Давайте рассмотрим простой пример базового приложения, разработанного в JavaFX: ToDoList, связанный с базой данных SQL.

  • База данных - это всего лишь одна таблица Task с идентификатором и полем TaskDescr VARCHAR.
  • Цель довольно проста: мы просто хотим отобразить задачу в TableView или ListView и добавить некоторые задачи.

Вот как выглядит наше приложение:

ToDoList GUI

Я решил разделить мой код на четыре части: DAO для классов, представляющих данные в таблице (Task.java), класс DAO, который обращается к базе данных (его поведение здесь не имеет значения). Модель, представляющая часть Model нашего TodoList (содержащая список задач и выполняемых над ними операций, вызов DAO и т. Д.). Представления FXML и контроллер:

Структура проекта

Далее вы можете найти код различных классов, которые нас интересуют (мы предполагали, что DAO в порядке (установка id выполняется автоматически), и мы не обрабатываем ошибки, чтобы упростить код:

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);
    }
}

Наконец, мой вопрос: хорош ли мой подход? Я имею в виду тот факт, что я создал ToDoListModel со списком задач, тот факт, что я обновляю свой список задач объектов в той же задаче, я обновляю свою базу данных с помощью DAO (создание в DAO будет выполнено после добавления в списке) и самое главное: какие операции я должен делать в handleAddButton моего контроллера? Здесь я сначала использовал метод add в моем TodoListModel, но этого недостаточно, потому что мой наблюдаемый список неправильно обновлен (добавленная задача появляется, но мы не можем выбрать ее с помощью мыши). Затем, когда я добавляю его также в элементы TableView, Задача появляется дважды и дважды добавляется в список.

В результате я понял, что ObservableList был связан со списком, который есть в моем ToDoListModel, но что мне делать, если я хочу выполнять операции с этим списком только в моей модели, но правильно обновлять ObservableList? (Выбираемый элемент и т. Д.)

Пример дублирования

Заранее благодарю за помощь и терпение, С уважением, Павел

1 ответ

Решение

Вот пример реализации
DAO Класс заботится о подключении к базе данных (может использовать пул или что-то еще). В этом случае это делает простое соединение.

public class DAO {
    public Connection getConnection() throws SQLException {
        return  DriverManager.getConnection("jdbc:mysql://192.168.40.5:3306/test", "root", "");
    }
}

ToDoListModel класс заботится о работе с базой данных, используя экземпляр DAO чтобы получить действительное соединение.

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;
                }
            }
        }
    }
}

Контроллер использует ToDoListModel инициализировать TableView элементы управления и операции добавления (редактирование и чтение - я их не реализовал, потому что придерживаюсь вашего кода)

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;
    }
}

Любые операции с базой данных асинхронны с CompletableFuture но вы можете использовать все, что вы предпочитаете. Важно помнить, что потоки пользовательского интерфейса могут быть созданы им только однозначно.

Другие вопросы по тегам