在填充 ObservableList 时,我是否必须从我的数据库中加载所有记录?

Kyl*_*uan 3 java javafx

所以我正在将我的 Swing Java 数据库应用程序移植到 Java FX(这里仍然是初学者,我最近刚刚学习了 FXML 和 MVC 模式的基础知识,所以请耐心等待)。

我打算将现有数据库中的数据加载到“学生”ObservableList 中,以便我可以在 TableView 上显示它,但是在我的原始 Swing 代码中,我有一个搜索 TextField,当用户单击按钮或按 Enter 时,该程序:

  1. 执行搜索特定记录并检索 RecordSet 的 SQLite 命令。
  2. 基于 RecordSet 内容创建一个 DefaultTableModel
  3. 并将该 TableModel 抛出到 JTable。

然而,Java FX 是一个完全不同的野兽(或者至少在我看来是这样——不要误会我的意思,我喜欢 Java FX :D)所以我不知道该怎么做。

所以,我的问题是,我是否必须加载数据库中的所有学生,然后使用一些 Java 代码过滤掉不符合搜索条件的学生(并在搜索文本为空白时显示所有学生),或者执行我仍然使用 SQLite 来过滤和检索记录(这意味着我需要清除列表然后在每次执行搜索时添加学生,也许它也会弄乱绑定?也许这种方法也会有速度损失? 除此之外,它还将重置当前选择的记录,因为我清除了列表——基本上,糟糕的 UI 设计会对可用性产生负面影响)

根据正确的方法,还有一个后续问题(抱歉,即使在谷歌搜索之后我也真的找不到这些问题的答案):

  • 如果我从数据库中获取所有学生并在 Java 中实现搜索功能,它会不会比它应该使用更多的 RAM,因为我将所有数据库数据存储在 RAM 中,而不仅仅是搜索的数据?我的意思是,当然,即使我的低端笔记本电脑也有 4GB 的内存,但是使用比我应该更多的内存的感觉让我感到有些内疚,哈哈
  • 如果我选择在每次执行新搜索时只更新 ObservableList 的内容,它会与绑定混淆吗?我是否必须再次设置绑定?在添加新内容之前如何清除 ObservableList 的内容?

我也有将选定的表项设置为与搜索字符串匹配的第一条记录的想法,但我认为这将很难使用,因为每次搜索只能突出显示一条记录。即使我们突出显示多行,也很难浏览所有选定的项目。

请给我正确的方法,而不是“简单”的方法。这是我第一次实现模式(MVC 还是我真的在做 MVP,我不知道),我意识到我以前的程序是多么难以维护和丑陋,因为我使用了自己的风格。这是一个相对较大的项目,我需要几年来支持和改进,因此拥有干净的代码和以正确的方式做事应该有助于维护该程序的功能。

非常感谢您的帮助,我希望我在问这些问题时不会被认为是“甚至不会谷歌的愚蠢人”。请多多包涵。

Jam*_*s_D 5

基本设计权衡

当然,您可以按照您描述的任何一种方式执行此操作。基本的权衡是:

  1. 如果您从数据库加载所有内容,并在 Java 中过滤表,则会使用更多内存(尽管没有您想象的那么多,如下所述)
  2. 如果您从数据库中过滤并在每次用户更改过滤器时重新加载,则显示数据时会有更大的延迟(延迟),因为将在数据库上执行新查询,(通常)数据库之间进行网络通信应用程序是最大的瓶颈(尽管还有其他瓶颈)。

数据库访问和并发

通常,您应该在后台线程上执行数据库查询(请参阅使用线程发出数据库请求);如果您经常进行数据库查询(即通过数据库进行过滤),这会变得复杂并且涉及在后台任务运行时频繁禁用 UI 中的控件。

TableView 设计和内存管理

JavaFXTableView是一个虚拟化控件。这意味着视觉组件(单元格)仅为可见元素创建(可能还有少量缓存)。当用户滚动时,这些单元格会被重用,根据需要显示不同的“项目”。可视化组件通常非常消耗内存(它们有数百个属性 - 颜色、字体属性、尺寸、布局属性等 - 其中大部分具有 CSS 表示),因此限制创建的数量可以节省大量内存,并且表视图可见部分的内存消耗基本上是恒定的,无论表的后备列表中有多少项。

一般内存消耗计算

items形成表的后备列表的可观察列表仅包含数据:不难大致估计给定大小的列表消耗的内存量。Strings 每个字符使用 2 个字节,加上一个小的固定开销,doubles 使用 8 个字节,ints 使用 4 个字节,等等。如果你在 JavaFX 属性中包装字段(推荐),每个都会有几个字节的开销;每个对象的开销约为 16 个字节,而引用本身通常最多使用 8 个字节。所以一个典型的Student存储几个字符串字段的对象通常会在内存中消耗几百字节的数量。(当然,例如,如果每个图像都有与之关联的图像,它可能会更多。)因此,如果您从数据库加载 100,000 名学生,您将使用大约 10-100MB 的 RAM,这在大多数个人计算机系统上都非常易于管理。

粗略的一般准则

通常,对于您描述的应用程序类型,我建议加载数据库中的内容并在内存中对其进行过滤。在我通常的工作领域(基因组学)中,我们有时需要数十或数百个实体,这是无法做到的。(例如,如果您的数据库包含美国公立学校的所有注册学生,您可能会遇到类似的问题。)

但是,作为一般的经验法则,对于“普通”对象(即没有大数据对象,例如与之关联的图像的对象),您的表大小将非常大,用户无法轻松管理(即使使用过滤),然后再认真地拉伸用户机器的内存容量。

在 Java 中过滤表(内存中的所有对象)

在代码中过滤非常简单。简而言之,您将所有内容加载到 中ObservableList,并将 包装在ObservableListFilteredList。AFilteredList包装了一个源列表和一个Predicate,它返回true一个项目应该通过过滤器(被包含)或者如果它被排除则返回false。

因此,您将使用的代码片段可能如下所示:

ObservableList<Student> allStudents = loadStudentsFromDatabase();
FilteredList<Student> filteredStudents = new FilteredList<>(allStudents);
studentTable.setItems(filteredStudents);
Run Code Online (Sandbox Code Playgroud)

然后您可以使用以下代码修改基于文本字段的谓词:

filterTextField.textProperty().addListener((obs, oldText, newText) -> {
    if (newText.isEmpty()) {
        // no filtering:
        filteredStudents.setPredicate(student -> true);
    } else {
        filteredStudents.setPredicate(student -> 
            // whatever logic you need:
            student.getFirstName().contains(newText) || student.getLastName().contains(newText));
    }
});
Run Code Online (Sandbox Code Playgroud)

本教程对过滤(和排序)表进行了更彻底的处理。

关于实现“通过查询过滤”的评论

如果您不想从数据库加载所有内容,则可以完全跳过过滤列表。查询数据库的速度几乎肯定不会在用户键入时进行过滤(使用新的数据库查询),因此您需要一个“更新”按钮(或文本字段上的动作侦听器)来重新计算新的过滤数据。您可能也需要在后台线程中执行此操作。您不需要在表的列上设置 new cellValueFactorys(或cellFactorys),或重新加载列;您只需studentTable.setItems(newListOfStudents);在数据库查询完成时调用即可。