JavaFX – 用于SQL查询的后台线程

我想知道是否有人可以帮助我一个相当烦人的问题,在JavaFX中创build一个后台线程! 我目前有几个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) { } } 

每个处理后的logging都被添加到链接到TableView或图表的ObservableList,或者简单地设置标签上的文本(取决于查询)。 我怎样才能在后台线程上执行查询,仍然可以免费使用接口,并从查询中更新

提前致谢

我创build了一个示例解决scheme,用于使用Task (如Alexander Kirov的注释中所build议的)访问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比没有任务的默认模式更复杂。 例如,在我的示例中,我禁用了启动任务的button,因此您不能在后台运行多个任务,执行相同的任务(这种处理类似于Web世界,您可能会禁用表单发布button来阻止forms被张贴)。 我还在执行长时间运行的数据库任务时向场景添加了一个animation进度指示器,以便用户指示正在进行的操作。

示例程序输出演示长时间运行的数据库操作正在进行时的UI体验(请注意,进度指示器在获取过程中呈现animation效果,这意味着虽然屏幕截图没有显示这一点,但界面仍可以响应):

databasefetcher

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

pipe理解决使用jewelsea提供的解决scheme。 值得注意的是,如果在不使用列表,表格和/或可观察列表(如果需要更新UI上的项目(如文本字段或标签))时实施此方法,则只需在Platform.runLater中添加更新代码即可。 以下是一些显示我的工作解决scheme的代码片段。

码:

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

唯一没有出现在这个实现中的东西是下面的,不知道为什么它不起作用,但是没有绘制graphics。

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