JavaFX - лучшие практики применения MVC с базой данных
Я новичок в JavaFX, и мне было интересно, каковы лучшие практики на этом языке для разработки приложения базы данных MVC, я думаю, мой вопрос будет довольно простым, если вы старший разработчик.
Давайте рассмотрим простой пример базового приложения, разработанного в JavaFX: ToDoList, связанный с базой данных SQL.
- База данных - это всего лишь одна таблица Task с идентификатором и полем TaskDescr VARCHAR.
- Цель довольно проста: мы просто хотим отобразить задачу в TableView или ListView и добавить некоторые задачи.
Вот как выглядит наше приложение:
Я решил разделить мой код на четыре части: 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
но вы можете использовать все, что вы предпочитаете. Важно помнить, что потоки пользовательского интерфейса могут быть созданы им только однозначно.