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

时间:2017-06-28 11:34:34

标签: java javafx

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

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

  1. 执行搜索特定记录的SQLite命令,并检索RecordSet。
  2. 根据RecordSet内容
  3. 创建DefaultTableModel
  4. 并将TableModel抛给JTable。
  5. 然而,Java FX是一个完全不同的野兽(至少对我来说似乎是这样 - 不要误解我,我喜欢Java FX:D)所以我不知道该怎么做

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

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

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

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

    请给我正确的方式,而不是" easy"办法。这是我第一次实施模式(MVC或我实际上在做MVP,我不知道),我意识到我之前的程序是多么难以维护和丑陋,因为我使用了自己的风格。这是一个相对较大的项目,我需要支持和改进好几年,所以拥有干净的代码并以正确的方式做事情应有助于维护该程序的功能。

    非常感谢您的帮助,我希望我不会成为一个甚至不能谷歌的愚蠢的人。在问这些问题。请在这里忍受我。

1 个答案:

答案 0 :(得分:4)

基本设计权衡

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

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

    通常,您应该在后台线程上执行数据库查询(请参阅Using threads to make database requests);如果您经常进行数据库查询(即通过数据库进行过滤),这会变得很复杂,并且在后台任务运行时经常禁用UI中的控件。

    TableView设计和内存管理

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

    一般内存消耗计算

    构成表的支持列表的items可观察列表仅包含数据:对于给定大小的列表所消耗的内存量并不难。 String每个字符使用2个字节,加上一个小的固定开销,double使用8个字节,int使用4个字节,等等。如果你将字段包装在JavaFX属性中(建议),每个都会有几个字节的开销;每个对象的开销约为16字节,引用本身通常最多使用8个字节。因此,存储一些字符串字段的典型Student对象通常会占用内存中几百字节的数量级。 (当然,如果每个都有一个与之关联的图像,例如,它可能会更多。)因此,如果您加载,比如说来自数据库的100,000名学生,则会耗尽10-100MB的RAM,这在大多数个人计算机系统上都是可管理的。

    粗略的一般准则

    通常,对于您描述的应用程序类型,我建议加载数据库中的内容并将其过滤到内存中。在我通常的工作领域(基因组学)中,我们有时需要10或100万个实体,这是不可能完成的。 (如果您的数据库包含all registered students in public schools in the USA,则可能会遇到类似的问题。)

    作为一般的经验法则,对于“普通”对象(即没有大型数据对象的对象,例如与之关联的图像),您的表格大小将非常大,以便用户轻松管理(在进行过滤之前),在严重拉伸用户机器的内存容量之前。

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

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

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

    ObservableList<Student> allStudents = loadStudentsFromDatabase();
    FilteredList<Student> filteredStudents = new FilteredList<>(allStudents);
    studentTable.setItems(filteredStudents);
    

    然后,您可以根据文本字段修改谓词,代码如下:

    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));
        }
    });
    

    This tutorial对过滤(和排序)表有更彻底的处理。

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

    如果您不想从数据库加载所有内容,则完全跳过已过滤的列表。查询数据库几乎肯定不能足够快地过滤(使用新的数据库查询)作为用户输入,因此您需要一个“更新”按钮(或文本字段上的动作侦听器),它重新计算新的过滤数据。您可能也需要在后台线程中执行此操作。您不需要在表的列上设置新的cellValueFactory s(或cellFactory s),也不需要重新加载列;您只需在数据库查询完成时调用studentTable.setItems(newListOfStudents);