我想知道有人可以帮助我一个相当烦人的问题,在
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中,该ObservableList链接到TableView或图形,或者简单地设置标签上的文本(取决于查询).如何在后台线程上执行查询,仍然可以让界面免费使用并从查询中更新
提前致谢
解决方法
我创建了一个
sample solution,用于使用
Task(如Alexander Kirov的评论建议)访问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比默认模式更复杂,没有任务,当一切都是单线程.例如,在我的示例中,我禁用启动任务的按钮,以便您不能在后台运行多个任务执行相同的操作(这种处理类似于Web界面,您可能会禁用窗体帖子按钮以防止形式被双重张贴).当长时间运行的数据库任务正在执行时,我还添加了一个动画进度指示器到现场,以便用户有一个迹象表明事情正在发生.
示例程序输出演示了当长时间运行的数据库操作正在进行时的UI体验(注意,进度指示器在抓取过程中动画化,这意味着UI通过屏幕截图不显示此响应):
为了将实现与并发任务的附加复杂性和功能与执行JavaFX应用程序线程上的所有内容的实现进行比较,可以看到another version of the same sample which does not use tasks.请注意,在我的情况下,使用玩具时,本地数据库基于任务的应用程序的额外复杂性是不必要的是因为本地数据库操作执行得如此之快,但是如果使用长时间运行的复杂查询连接到大型远程数据库,那么基于任务的方法是值得的,因为它为用户提供了更流畅的UI体验.