我不知道是否有人能帮助我与有关创建JavaFX中后台线程,而恼人的问题! 我目前有数据添加到当前的JavaFX应用程序的线程中运行UI几个SQL查询(见下面的例子)。 然而,当这些查询的执行它冻结UI,因为它不是在后台线程上运行。 我看着那个使用任务和排序的了解他们不同的例子,但我不能让他们做数据库查询,其中一些需要几秒钟的运行时工作。
这里是一个执行查询的方法之一:
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) {
}
}
每个处理过的记录被添加到链接到一个TableView中,或图形或简单地设置一个标签(取决于查询)上的文本的ObservableList。 我怎么能执行一个后台线程的查询,仍然离开界面自由使用,从查询更新
提前致谢
我创建了一个样品溶液使用一个任务 (如亚历山大基洛夫的评论建议),以在同时执行的线程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体验,当一个长时间运行的数据库操作过程中(注意时,这意味着UI响应虽然屏幕截图不会显示这个取指示灯动画的进度):
要比较并发任务与该JavaFX应用程序线程上执行一切的实现实现额外的复杂性和功能,你可以看到不使用任务的同一样品的另一个版本 。 请注意,在我的情况下,用玩具,本地数据库的基于任务的应用程序的额外的复杂性是不必要的,因为在本地数据库操作执行的那么快,但如果你是使用长时间运行的复杂查询连接到大型远程数据库,比基于任务做法是值得的,因为它提供了一个更流畅的UI体验的用户。
管理使用jewelsea提供的解决方案来解决。 值得注意的是,如果你需要更新的用户界面中的项目,如文本字段或标签,然后只需在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;
}