我正在try 创建一个带有监听程序的搜索栏,以便对javafx中的数据进行实时搜索和过滤.当我单击启动程序的Execute按钮时,应该会触发dataSearch方法.起初,我的搜索栏似乎工作得很好,但问题是,当我从应用程序更新我的Oracle数据库时,TableView不再根据搜索栏中的输入关键字进行过滤.似乎在每次执行程序时,dataSearch方法只被调用一次,并且在TableView被修改之后,被调用的ObservableList不会自动更新.

代码示例:

public class App extends Application {
    private TableView<General> generalTableView;
    private ObservableList<General> generalList;
    private TextField searchBox;
    private TextField addTextField;

    @Override
    public void start(Stage primaryStage) {
        generalTableView = new TableView<>();
        TableColumn<General, String> generalColumn = new TableColumn<>("Col");
        generalColumn.setCellValueFactory(new PropertyValueFactory<>("col"));
        generalTableView.getColumns().add(generalColumn);

        Button loadDataButton = new Button("Load Data");
        loadDataButton.setOnAction(event -> {
            createGeneralTable();
            loadGeneralData();
            dataSearch();
        });

        searchBox = new TextField();
        searchBox.setPromptText("Search");

        addTextField = new TextField();
        addTextField.setPromptText("Add Data");

        Button addButton = new Button("Add");
        addButton.setOnAction(event -> addData(addTextField.getText()));

        HBox searchBoxContainer = new HBox(searchBox);
        HBox addBoxContainer = new HBox(addTextField, addButton);

        VBox root = new VBox(generalTableView, loadDataButton, searchBoxContainer, addBoxContainer);

        Scene scene = new Scene(root, 400, 400);
        primaryStage.setScene(scene);
        primaryStage.show();

        loadDataButton.fire(); // Trigger the button action when the program starts
    }

    public static void main(String[] args) {
        launch(args);
    }

    static class General {
        private ObjectProperty<String> col;

        public General(String col) {
            this.col = new SimpleObjectProperty<>(col);
        }

        public String getCol() {
            return col.get();
        }

        public void setCol(String col) {
            this.col.set(col);
        }

        public ObjectProperty<String> colProperty() {
            return col;
        }
    }

    static class OracleConnect {
        public static Connection getConnection() {
            Connection connection = null;
            try {
                Class.forName("oracle.jdbc.driver.OracleDriver");
                connection = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:XE", "system", "o4a75e");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return connection;
        }
    }

    private void createGeneralTable() {
        try {
            Connection connection = OracleConnect.getConnection();
            String createTableGeneral = "CREATE TABLE general (col VARCHAR2(50))";
            PreparedStatement statement = connection.prepareStatement(createTableGeneral);
            statement.execute();

            statement.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private void loadGeneralData() {
        try {
            Connection connection = OracleConnect.getConnection();
            String query = "SELECT col FROM general";
            PreparedStatement statement = connection.prepareStatement(query);
            ResultSet resultSet = statement.executeQuery();
            generalList = FXCollections.observableArrayList();

            while (resultSet.next()) {
                String col = resultSet.getString("col");
                General general = new General(col);
                generalList.add(general);
            }

            generalTableView.setItems(generalList);

            resultSet.close();
            statement.close();
            connection.close();

              
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private void dataSearch() {
        ObservableList<General> generalList = FXCollections.observableArrayList(generalTableView.getItems());

        FilteredList<General> filteredData = new FilteredList<>(generalList, b -> true);

        searchBox.textProperty().addListener((observable, oldValue, newValue) -> {
            filteredData.setPredicate(general -> {
                if (newValue.isEmpty() || newValue.isBlank() || newValue == null) {
                    return true;
                }

                String searchKeyword = newValue.toLowerCase();
                if (general.getCol().toLowerCase().contains(searchKeyword)) {
                    return true;
                } else {
                    return false;
                }
            });
        });

        SortedList<General> sortedData = new SortedList<>(filteredData);
        sortedData.comparatorProperty().bind(generalTableView.comparatorProperty());

        generalTableView.setItems(sortedData);
    }

    private void addData(String data) {
        try {
            Connection connection = OracleConnect.getConnection();
            String insertQuery ="INSERT INTO general (col) VALUES (?)";
            PreparedStatement statement = connection.prepareStatement(insertQuery);
            statement.setString(1, data);
            statement.executeUpdate();

            General general = new General(data);
            generalList.add(general);

            statement.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

推荐答案

这个答案用于过滤添加到内存列表中的数据,支持TableView,因此不包括数据库代码.

您在问题中的代码中遇到的主要问题是不必要地创建新的数据列表.相反,只要有一个数据列表并在任何地方使用它,就可以将其包装在一个FilteredList和一个SortedList中,以便对底层列表数据进行所需的转换.

确保创建的对象和集合的实例不超过所需数量的一个好方法是将引用声明为Final,这是示例代码中演示的技术.

示例:已排序、筛选的TableView

Cat.java

创建记录以保存您的数据.

package com.example.cats;

record Cat(String name) {}

IronCatFistClan.java

创建一个模型来保存您的记录,并对其进行排序和筛选.

你可以干脆把它叫做Model而不是IronCatFistClan.

package com.example.cats;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;

class IronFistCatClan {
    private final ObservableList<Cat> cats = FXCollections.observableArrayList(
            new Cat("Grizabella"),
            new Cat("Bombalurina"),
            new Cat("Rum Tum Tugger")
    );

    private final FilteredList<Cat> filteredCats = new FilteredList<>(
            cats
    );
    private final SortedList<Cat> sortedFilteredCats = new SortedList<>(
            filteredCats
    );

    public ObservableList<Cat> getCats() {
        return cats;
    }

    public SortedList<Cat> getSortedFilteredCats() {
        return sortedFilteredCats;
    }

    public void setSearchText(String searchText) {
        filteredCats.setPredicate(
                cat ->
                        containsCaseInsensitive(
                                cat.name(),
                                searchText
                        )
        );
    }

    private static boolean containsCaseInsensitive(String textToSearch, String searchText) {
        if (searchText == null || searchText.isEmpty() || searchText.isBlank()) {
            return true;
        }

        return textToSearch.toLowerCase().contains(
                searchText.toLowerCase()
        );
    }
}

FilteredCatApp.java

创建一个与模型交互的用户界面,显示数据的表视图,具有设置数据筛选器的搜索功能和向数据添加新记录的添加功能.

package com.example.cats;

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;

public class FilteredCatApp extends Application {
    @Override
    public void start(Stage primaryStage) {
        IronFistCatClan ironFistCatClan = new IronFistCatClan();

        VBox root = new VBox(
                10,
                createTable(ironFistCatClan),
                createSearchBox(ironFistCatClan),
                createAddControls(ironFistCatClan)
        );
        root.setPadding(new Insets(10));

        Scene scene = new Scene(root, 400, 400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private static HBox createAddControls(IronFistCatClan ironFistCatClan) {
        TextField newCatName = new TextField();
        newCatName.setPromptText("Name of a cat to add");

        Button addButton = new Button("Add Cat");
        addButton.setDefaultButton(true);
        addButton.setOnAction(event ->
                ironFistCatClan.getCats().add(
                        new Cat(newCatName.getText())
                )
        );

        return new HBox(10, newCatName, addButton);
    }

    private static TextField createSearchBox(IronFistCatClan ironFistCatClan) {
        TextField searchBox = new TextField();

        searchBox.setPromptText("Search");
        searchBox.textProperty().addListener((observable, oldSearchText, newSearchText) ->
                ironFistCatClan.setSearchText(newSearchText)
        );

        return searchBox;
    }

    private static TableView<Cat> createTable(IronFistCatClan ironFistCatClan) {
        TableView<Cat> tableView = new TableView<>(
                ironFistCatClan.getSortedFilteredCats()
        );

        ironFistCatClan.getSortedFilteredCats().comparatorProperty().bind(
                tableView.comparatorProperty()
        );

        TableColumn<Cat, String> nameColumn = new TableColumn<>("Name");
        nameColumn.setCellValueFactory(p ->
                new SimpleStringProperty(p.getValue().name())
        );
        tableView.getColumns().add(nameColumn);

        return tableView;
    }

    public static void main(String[] args) {
        launch(args);
    }
}

FAQ

我是否应该创建一个侦听器来跟踪数据库表中的更改,并在更改发生时执行搜索方法?

不是的.我不建议你这么做.

或许您可以创建一个database trigger来监视数据更改.触发器可以调用Java代码(我不知道如何做)来通知数据库中的数据已经更改.Java代码可以查询数据库中的新数据,并将更改事件通知监听器.然而,这看起来像是一种xy problem.

支持TableView的数据是数据库中数据的内存副本.在并发数据库更新时,它可能会变得不同步.保持数据同步的一种方法是让数据库通知并将更改推送到应用程序(就像上面概述的触发器概念一样).

然而,更常见的方法是从数据库every time检索您想要基于新数据更新UI的当前数据.应用程序在需要时从数据库中提取当前数据,而不是try 让数据库发布数据已更改的事件.

这可能表明,当我单击运行该程序的按钮时,包含侦听器的搜索方法只执行一次.

不是的.您的方法dataSearch()从您的方法loadGeneralData中调用,该方法在您的loadDataButtononAction事件处理程序中执行.在应用程序启动时单击该按钮,但如果用户手动按下该按钮,也可以执行该方法.

每次调用dataSearch()时,您都会向搜索文本添加一个新的侦听器.你可能不想这么做.每次你按下loadDataButton个新的监听器都会被添加,而旧的监听器不会被删除,它们最终会保留对旧数据列表的引用,这是一种错误. 在添加新的监听器之前,您至少应该删除旧的监听器,以保留这种方法.

一个更好的解决方案是一种MVC,在这里你与一个可观察的数据模型进行交互,更新它的各个方面,比如模型中的数据,或者根据需要进行过滤和排序(如本答案中的示例所示).

如果需要(此处未显示),可以将事务持久化服务和数据访问对象添加到应用程序中,以便根据需要将模型代码与数据库对接.

Database相关问答推荐

为什么postgres枚举需要4个字节?

华为Appcube中的对象字段类型

生产中的超大型 Mnesia 表

MySQL解释更新

单元测试:用于测试的数据库设置

Postgres pg_dump 每次都以不同的顺序转储数据库

如何识别 DB2 端口号

PHP - 数据库抽象层使用静态类与单例对象?

如何在实体-属性-值设计中处理不同的数据类型(例如,具有多列的单个表或每种数据类型的多个表)?

Django:将博客条目查看次数加一,这有效率吗?

什么是非关系数据库的例子?

如何动态更改 Ruby on Rails 中所有模型的 Active Record 数据库?

Google 的 Bigtable 与关系数据库

如何在 SQL Server 中生成并手动插入唯一标识符?

存储信用卡号 - PCI?

如何使用 JPA 注释创建连接表?

Data Mapper 是不是比 Active Record 更现代的趋势

列的 SQL Server 2008 千位分隔符

为什么 Rails 迁移在应用程序中定义外键而不在数据库中定义外键?

清空数据库是什么意思?