JavaFX - SQL查询的后台线程

Uni*_*qum 11 javafx javafx-2

我想知道是否有人可以帮助我解决在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) {
    }
}
Run Code Online (Sandbox Code Playgroud)

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

提前致谢

jew*_*sea 17

我创建了一个示例解决方案,用于使用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;
  }
}    
Run Code Online (Sandbox Code Playgroud)

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

示例程序输出演示了在长时间运行的数据库操作正在进行时的UI体验(请注意,在获取期间,进度指示器是动画的,这意味着虽然屏幕截图没有显示,但UI仍然是响应的):

databasefetcher

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