【问题标题】:JavaFX - MVC Application best practices with databaseJavaFX - 使用数据库的 MVC 应用程序最佳实践
【发布时间】:2018-01-16 08:48:32
【问题描述】:

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

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

  • 数据库只是一个具有 idtaskDescr VARCHAR 字段的表 Task
  • 目的很简单:我们只想在 TableView 或 ListView 中显示任务并能够添加一些任务。

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

ToDoList GUI

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

Project structure

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

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

Duplication example

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

【问题讨论】:

  • 没有错误处理的逻辑。在 UI 线程中使用数据库并不好。 ToDoListModelObservableList 配合得很好,不需要保留它的实例(她坐在控件中)。
  • handleAddButton 可以使用ToDoListModel 立即将元素保存在数据库中,但是在进行小的更改时这很有用。当您进行更多编辑时,最好制作一个单独的保存按钮,该按钮仅允许向数据库发出一个请求 (upsert) 来保存整个列表,并允许一个 delete 删除。
  • 嗨,是的,正如我所说,我没有编写错误处理代码只是为了简化此处发布的代码。好的,所以我必须用 ObservableList 替换 TaskListModel 中的 List 并对其进行操作?我想我不明白你的第二个帖子:/

标签: java database model-view-controller javafx fxml


【解决方案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 异步,但您可以使用任何您喜欢的方式。重要的是要记住 UI 线程只能由它创建。

【讨论】:

  • 好吧,我明白你的意思了,所以据我了解,经典的“模型-视图-控制器”模式无法在 JavaFX 中实现,因为模型(通常携带所有数据并与与数据库交互的 DAO 类)由控制器本身在 JavaFX 中表示?我认为在处理大量数据(数据库数据和无数据库数据)的繁重应用程序中,我们使用了一个模型来管理所有思考并将其提供给控制器。 “UI线程只能由它独特地制作。” -> 我没明白这一点:/ ?
  • 哦,我明白了,所以你的意思是我的this.model.addTask(t) 改变了用户界面?我在之前的评论中是否正确理解了 MVC 原则?必须管理模型中其他数据的重型应用程序呢?
  • @PaulThirozier,是的,您使用的数据(模型)的所有更改都会反映在用户界面中。因此,这些更改只能在应用程序线程中完成。在您的示例中,没有异步数据加载,因此无需担心(除了用户界面将冻结)。这是大型项目的方法。这里的模型是Task,如果你习惯了Swing的架构,这可能会让你感到困惑(需要做一个模型来保持数据的收集)
  • 哦,是的,我认为我对这些不同的方法有点困惑。所以,据我所知,如果我构建一个繁重的 JavaFX 应用程序,我应该考虑使用异步数据加载的方法(我理解你的代码,谢谢你),如果我需要处理不在数据库中的其他数据,我创建每种数据类型的另一个类。但是,为所有数据实现“ModelContainer”不是一个好习惯?
  • 这就是问题所在。 MVC 类型架构背后的驱动动机是使同一数据(模型)的多个视图成为可能。如果从一个视图修改数据,则其他视图应该能够更新(当然,这是在模型不依赖于视图的情况下完成的)。您的设计不允许这样做:想象一下,例如相同数据的ListView(可能在另一个选项卡中)。如果表格和列表都可以修改数据,那么您的设计中就没有通信机制,因为表格数据可访问的唯一位置是在控制器中。
猜你喜欢
  • 2010-10-12
  • 1970-01-01
  • 2020-05-05
  • 2010-11-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-07
相关资源
最近更新 更多