当前位置: 首页 > 知识库问答 >
问题:

Java FX-SQL查询的后台线程

司空元凯
2023-03-14

我想知道是否有人能帮我解决一个关于在JavaFX中创建后台线程的恼人问题!我目前有几个SQL查询,它们向当前运行在JavaFX应用程序线程上的UI添加数据(参见下面的例子)。然而,当执行这些查询时,它会冻结UI,因为它不是在后台线程上运行的。我已经查看了各种使用Task的示例,并对它们有所了解,但是在进行数据库查询时,我无法让它们工作,有些查询需要几秒钟才能运行。

下面是执行查询的方法之一:

public void getTopOrders() {
    customerOrders.clear();
    try {
        Connection con = DriverManager.getConnection(connectionUrl);
        //Get all records from table
        String SQL = "EXEC dbo.Get_Top_5_Customers_week";
        ResultSet rs;
        try (Statement stmt = con.createStatement();) {
            rs = stmt.executeQuery(SQL);

            while (rs.next()) {
                double orderValue = Double.parseDouble(rs.getString(3));
                customerOrders.add(new CustomerOrders(rs.getString(1),
                        rs.getString(2), "£" + formatter.format(orderValue),
                        rs.getString(4).substring(6, 8) + "/" + 
                        rs.getString(4).substring(4, 6) + "/" + 
                        rs.getString(4).substring(0, 4)));
            }
        }

    } catch (SQLException | NumberFormatException e) {
    }
}

每个处理过的记录都被添加到一个ObservableList中,该列表链接到一个TableView或graph,或者只是在一个标签上设置文本(取决于查询)。我如何在后台线程上执行查询,同时仍然让接口可以自由使用并从查询中更新

提前谢谢

共有2个答案

曾晨
2023-03-14

设法使用珠宝海提供的解决方案解决问题。值得注意的是,如果在不使用列表、表和/或可观察列表的情况下实现此方法,而您需要更新UI上的项目,例如文本字段或标签,那么只需在Platform.runLater中添加更新代码即可。下面是一些代码片段,显示了我的工作解决方案。

代码:

public void getSalesData() {
    try {
        Connection con = DriverManager.getConnection(connectionUrl);
        //Get all records from table
        String SQL = "EXEC dbo.Order_Information";
        try (Statement stmt = con.createStatement(); ResultSet rs =
                        stmt.executeQuery(SQL)) {
            while (rs.next()) {

                todayTot = Double.parseDouble(rs.getString(7));
                weekTot = Double.parseDouble(rs.getString(8));
                monthTot = Double.parseDouble(rs.getString(9));
                yearTot = Double.parseDouble(rs.getString(10));
                yearTar = Double.parseDouble(rs.getString(11));
                monthTar = Double.parseDouble(rs.getString(12));
                weekTar = Double.parseDouble(rs.getString(13));
                todayTar = Double.parseDouble(rs.getString(14));
                deltaValue = Double.parseDouble(rs.getString(17));

                yearPer = yearTot / yearTar * 100;
                monthPer = monthTot / monthTar * 100;
                weekPer = weekTot / weekTar * 100;
                todayPer = todayTot / todayTar * 100;

                //Doesn't update UI unless you add the update code to Platform.runLater...
                Platform.runLater(new Runnable() {
                    public void run() {
                        todayTotal.setText("£" + formatter.format(todayTot));
                        weekTotal.setText("£" + formatter.format(weekTot));
                        monthTotal.setText("£" + formatter.format(monthTot));
                        yearTotal.setText("£" + formatter.format(yearTot));
                        yearTarget.setText("£" + formatter.format(yearTar));
                        monthTarget.setText("£" + formatter.format(monthTar));
                        weekTarget.setText("£" + formatter.format(weekTar));
                        todayTarget.setText("£" + formatter.format(todayTar));
                        yearPercent.setText(percentFormatter.format(yearPer) + "%");
                        currentDelta.setText("Current Delta (Week Ends): £"
                                + formatter.format(deltaValue));
                    }
                });

            }
        }

    } catch (SQLException | NumberFormatException e) {
    }
}


    public void databaseThreadTester() {
    fetchDataFromDB();
}

private void fetchDataFromDB() {
    final testController.FetchNamesTask fetchNamesTask = new testController.FetchNamesTask();
    databaseActivityIndicator.setVisible(true);
    databaseActivityIndicator.progressProperty().bind(fetchNamesTask.progressProperty());
    fetchNamesTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
        @Override
        public void handle(WorkerStateEvent t) {
        }
    });
    fetchNamesTask.runningProperty().addListener(new ChangeListener<Boolean>() {
        @Override
        public void changed(ObservableValue<? extends Boolean> observable, Boolean wasRunning, Boolean isRunning) {
            if (!isRunning) {
                databaseActivityIndicator.setVisible(false);
            }
        }
    ;
    });
databaseExecutor.submit(fetchNamesTask);
}

abstract class DBTask<T> extends Task {

    DBTask() {
        setOnFailed(new EventHandler<WorkerStateEvent>() {
            @Override
            public void handle(WorkerStateEvent t) {
            }
        });
    }
}

class FetchNamesTask extends testController.DBTask {

    @Override
    protected String call() throws Exception {

        fetchNames();

        return null;
    }

    private void fetchNames() throws SQLException, InterruptedException {
        Thread.sleep(5000);
        getTopOrders();
        getSalesData();
    }
}

这个实现唯一不工作的地方是下面的,不知道为什么它不工作,但是它没有画出图形。

public void addCricketGraphData() {

    yearChart.getData().clear();
    series.getData().clear();
    series2.getData().clear();
    try {
        Connection con = DriverManager.getConnection(connectionUrl);
        //Get all records from table
        String SQL = "...omitted...";
        try (Statement stmt = con.createStatement(); ResultSet rs =
                        stmt.executeQuery(SQL)) {
            while (rs.next()) {
                Platform.runLater(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            series.getData().add(new XYChart.Data<String, Number>(rs.getString(1),
                                    Double.parseDouble(rs.getString(7))));
                            series2.getData().add(new XYChart.Data<String, Number>(rs.getString(1),
                                    Double.parseDouble(rs.getString(8))));
                        } catch (SQLException ex) {
                            Logger.getLogger(testController.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                });

            }
        }

    } catch (SQLException | NumberFormatException e) {
    }
    yearChart = createChart();
}

 protected LineChart<String, Number> createChart() {
    final CategoryAxis xAxis = new CategoryAxis();
    final NumberAxis yAxis = new NumberAxis();

    // setup chart
    series.setName("Target");
    series2.setName("Actual");
    xAxis.setLabel("Period");
    yAxis.setLabel("£");

    //Add custom node for each point of data on the line chart.
    for (int i = 0; i < series2.getData().size(); i++) {
        nodeCounter = i;
        final int value = series.getData().get(nodeCounter).getYValue().intValue();
        final int value2 = series2.getData().get(nodeCounter).getYValue().intValue();
        int result = value2 - value;

        Node node = new HoveredThresholdNode(0, result);
        node.toBack();
        series2.getData().get(nodeCounter).setNode(node);
    }

    yearChart.getData().add(series);
    yearChart.getData().add(series2);

    return yearChart;
}
田念
2023-03-14

我创建了一个示例解决方案,使用一个任务(如亚历山大·基洛夫的评论中所建议的)在JavaFX应用程序线程的并发执行线程上访问数据库。

示例解决方案的相关部分如下所示:

// fetches a collection of names from a database.
class FetchNamesTask extends DBTask<ObservableList<String>> {
  @Override protected ObservableList<String> call() throws Exception {
    // artificially pause for a while to simulate a long 
    // running database connection.
    Thread.sleep(1000); 

    try (Connection con = getConnection()) {
      return fetchNames(con);
    }
  }

  private ObservableList<String> fetchNames(Connection con) throws SQLException {
    logger.info("Fetching names from database");
    ObservableList<String> names = FXCollections.observableArrayList();

    Statement st = con.createStatement();      
    ResultSet rs = st.executeQuery("select name from employee");
    while (rs.next()) {
      names.add(rs.getString("name"));
    }

    logger.info("Found " + names.size() + " names");

    return names;
  }
}

// loads a collection of names fetched from a database into a listview.
// displays a progress indicator and disables the trigge button for
// the operation while the data is being fetched.
private void fetchNamesFromDatabaseToListView(
        final Button triggerButton, 
        final ProgressIndicator databaseActivityIndicator, 
        final ListView listView) {
  final FetchNamesTask fetchNamesTask = new FetchNamesTask();
  triggerButton.setDisable(true);
  databaseActivityIndicator.setVisible(true);
  databaseActivityIndicator.progressProperty().bind(fetchNamesTask.progressProperty());
  fetchNamesTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
    @Override public void handle(WorkerStateEvent t) {
      listView.setItems(fetchNamesTask.getValue());
    }
  });
  fetchNamesTask.runningProperty().addListener(new ChangeListener<Boolean>() {
    @Override public void changed(ObservableValue<? extends Boolean> observable, Boolean wasRunning, Boolean isRunning) {
      if (!isRunning) {
        triggerButton.setDisable(false);
        databaseActivityIndicator.setVisible(false);
      }
    };
  });
  databaseExecutor.submit(fetchNamesTask);
}

private Connection getConnection() throws ClassNotFoundException, SQLException {
  logger.info("Getting a database connection");
  Class.forName("org.h2.Driver");
  return DriverManager.getConnection("jdbc:h2:~/test", "sa", "");
}  

abstract class DBTask<T> extends Task<T> {
  DBTask() {
    setOnFailed(new EventHandler<WorkerStateEvent>() {
      @Override public void handle(WorkerStateEvent t) {
        logger.log(Level.SEVERE, null, getException());
      }
    });
  }
}

// executes database operations concurrent to JavaFX operations.
private ExecutorService databaseExecutor = Executors.newFixedThreadPool(
  1, 
  new DatabaseThreadFactory()
);  

static class DatabaseThreadFactory implements ThreadFactory {
  static final AtomicInteger poolNumber = new AtomicInteger(1);

  @Override public Thread newThread(Runnable runnable) {
    Thread thread = new Thread(runnable, "Database-Connection-" + poolNumber.getAndIncrement() + "-thread");
    thread.setDaemon(true);

    return thread;
  }
}    

请注意,一旦你开始同时执行操作,当所有内容都是单线程时,你的编码和 UI 会比没有任务的默认模式更复杂。例如,在我的示例中,我禁用了启动 Task 的按钮,因此您不能让多个任务在后台运行执行相同的操作(这种处理类似于 Web 世界,您可以在其中禁用表单发布按钮以防止表单被双重发布)。我还在长时间运行的数据库任务执行时向场景中添加了一个动画进度指示器,以便用户能够指示正在发生某些事情。

当长时间运行的数据库操作正在进行时,演示用户界面体验的示例程序输出(注意,在获取过程中进度指示器处于动画状态,这意味着用户界面是响应式的,尽管屏幕截图没有显示这一点):

为了比较具有并发任务的实现与在 JavaFX 应用程序线程上执行所有内容的实现的额外复杂性和功能,您可以看到不使用任务的同一示例的另一个版本。请注意,在我使用玩具本地数据库的情况下,基于任务的应用程序的额外复杂性是不必要的,因为本地数据库操作执行得如此之快,但是如果您使用长时间运行的复杂查询连接到大型远程数据库,则基于任务的方法是值得的,因为它为用户提供了更流畅的UI体验。

 类似资料:
  • 我一直在尝试用来自 mysql 填充 JavaFX 。我已经搜索了一个多星期了,在我偶然发现这个站点上的类似问题JavaFX - SQL查询的背景线程之前。那里的代码有点帮助,但我遇到的唯一问题是查询从数据库中选择单个列。 我需要的是显示 JavaFX 表。如果有人能帮助我解决这个问题,我将不胜感激。

  • 我原以为这是一个简单的问题,但我很难找到答案。我有一个与JavaFX场景对象关联的ImageView对象,我想从磁盘加载大图像,并使用ImageView一个接一个地显示它们。我一直在努力寻找一种好方法来反复检查图像对象,当它在后台加载完成时,将其设置为ImageView,然后开始加载新的图像对象。我提出的代码(如下)有时有效,有时无效。我很确定我在JavaFX和线程方面遇到了问题。它有时加载第一个

  • 我试图记录从。xml文件调用的sql查询。我面临的问题是,当我看到日志时,它没有很好地格式化。另外,我不知道为什么它会重复... [主]信息org.dozer.dozerBeanMapper-初始化一个新的dozer bean Mapper实例。[主]信息org.dozer.dozerBeanMapper-初始化一个新的dozer bean Mapper实例。[main]信息org.springf

  • 我正在查询Sql Server并返回一个List-我想使用此List作为我的组合框的源。下面是我正在使用的代码,它运行时没有错误,但我的组合框始终为空并且从不填充。这里有什么不正确的? 主要的Java语言 样品fxml

  • 诸葛io能够帮助企业采集和分析海量的用户行为数据,我们提供了非常灵活的可视化分析功能,能够很好的满足产品团队、市场团队、运营团队和管理决策层的日常分析需求。 但是,很多企业中会有专门的数据分析师协助业务和管理团队进行数据分析工作。针对不同的业务需求,数据分析师经常需要对用户行为数据展开更为复杂的、更为深入的查询、分析和洞察,这时,诸葛io的可视化分析功能对他们来说是不够灵活和强大的。 为此,我们提

  • 我正在尝试为iReport获取等效查询。这是我的sql查询。但是当我将它粘贴到Ireport数据集时,我得到了这个错误。