JavaFX - MVC 应用程序与数据库的最佳实践

JavaFX - MVC Application best practices with database

我是 JavaFX 的新手,我想知道用这种语言开发 MVC 数据库应用程序的最佳实践是什么,如果您是高级开发人员,我想我的问题会很简单。

让我们考虑一个用 JavaFX 开发的基本应用程序的简单示例:一个与 SQL 数据库链接的 ToDoList。

这就是我们的应用程序的样子:

ToDoList GUI

我决定将我的代码分成四个部分,DAO 用于代表 table 中的数据的 classes(Task.java ),访问数据库的 DAO class(它的行为在这里无关紧要)。 model 代表我们 TodoList 的模型部分(包含任务列表并对其执行操作,调用 DAO 等)。 FXML ViewsController :

Project structure

接下来,您可以找到我们感兴趣的不同 classes 的代码(我们假设 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,事实上我更新了我的对象列表 Task 在同一个任务中我用 DAO 更新了我的数据库(a DAO 中的创建将在列表中添加之后执行)和最重要的:我应该在控制器的 handleAddButton 中执行哪些操作?在这里,我首先在我的 TodoListModel 中使用了 add 方法,但这还不够,因为我的可观察列表被错误地更新了(添加的任务出现了,但我们不能用鼠标 select 它)。然后,当我也将它添加到 TableView 项目中时,任务出现了两次并且已在列表中添加了两次。

因此,我了解到 ObservableList 已链接到我的 ToDoListModel 中的列表,但是如果我只想在我的模型中对该列表进行操作但要更新 ObservableList,我应该怎么做正确吗? (选择table 项等...)

Duplication example

提前感谢您的帮助和耐心等待, 真挚地, 保罗

这是一个示例实现
DAO class 负责连接到数据库(可能使用池或其他东西)。在这种情况下,它建立了一个简单的连接。

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

ToDoListModel class 通过使用 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 异步,但您可以使用任何您喜欢的方式。重要的是要记住 UI 线程只能由它唯一地创建。