タスク(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はタスクなしのデフォルトモードよりも複雑になることに注意してください。たとえば、私のサンプルでは、タスクを開始するボタンを無効にしたため、バックグラウンドで複数のタスクを実行して同じことを実行することはできません(この種の処理は、フォーム投稿ボタンを無効にして、フォームは二重に投稿されています)。また、長時間実行されているデータベースタスクの実行中に、アニメーション化された進行状況インジケーターをシーンに追加して、ユーザーが何かが起こっていることを示すことができるようにしました。
長時間実行されているデータベース操作が進行中の場合のUIエクスペリエンスを示すサンプルプログラム出力(進行状況インジケーターがフェッチ中にアニメーション化されていることに注意してください。これは、スクリーンショットにはこれが表示されていなくても、UIが応答することを意味します)。
並行タスクを使用する実装と、JavaFXアプリケーションスレッドですべてを実行する実装の追加の複雑さと機能を比較するために、タスクを使用しない同じサンプルの別のバージョンを確認できます。おもちゃのローカルデータベースの場合、ローカルデータベースの操作は非常に高速に実行されるため、タスクベースのアプリケーションをさらに複雑にする必要はありませんが、タスクベースのクエリよりも長時間実行される複雑なクエリを使用して大規模なリモートデータベースに接続している場合は注意してください。このアプローチは、ユーザーにスムーズなUIエクスペリエンスを提供するため、価値があります。