2013-02-14 23 views
10

我想知道是否有人可以幫助我一個相當煩人的問題,在JavaFX中創建一個後臺線程!我目前有幾個SQL查詢將數據添加到當前在JavaFX應用程序線程上運行的UI(請參見下面的示例)。但是,當這些查詢執行時,它會凍結UI,因爲它不在後臺線程上運行。我查看了使用Task的各種示例並理解它們,但是在執行數據庫查詢時我無法讓它們工作,其中一些需要幾秒鐘才能運行。JavaFX - SQL查詢的後臺線程

下面是該方法的一個執行的查詢:

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(取決於查詢)。我怎麼能執行一個後臺線程的查詢,仍然留下自由提前使用,並從查詢更新

感謝

+1

您是否觀察過javafx.concurrent包中的類? docs.oracle.com/javafx/2/api/javafx/concurrent/...。我認爲,Task類應該對你特別有趣:docs.oracle.com/javafx/2/api/javafx/concurrent/Task.html,你可以閱讀它的javadoc,瞭解完整的選項列表。 – 2013-02-14 20:26:28

+0

也許DataFX可以幫助您:http://www.javafxdata.org或http://www.guigarage.com/category/datafx/ – 2013-02-15 13:32:58

回答

14

我創建了一個sample solution使用一個Task(該接口在亞歷山大基洛夫的評論建議)訪問併發執行線程上的數據庫到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; 
    } 
}  

需要注意的是,一旦你開始同時做的事情,你的編碼和您的用戶界面變得比沒有任務的默認模式更加複雜,當一切都是單線程。例如,在我的示例中,我禁用了啓動任務的按鈕,因此您不能讓在後臺運行的多個任務執行相同的任務(這種處理類似於Web世界,您可能會禁用表單發佈按鈕來阻止形式被張貼)。我還在執行長時間運行的數據庫任務時向場景添加了一個動畫進度指示器,以便用戶知道正在發生的事情。

樣本程序的輸出演示的時候長時間運行的數據庫操作過程中的UI體驗(注意進度指示器中,這意味着UI響應雖然屏幕截圖不會顯示該抓取動畫):

databasefetcher

要比較實現與併發任務相比在JavaFX應用程序線程上執行所有任務的實現的額外複雜性和功能,可以看到another version of the same sample which does not use tasks。請注意,對於玩具本地數據庫而言,基於任務的應用程序的額外複雜性是不必要的,因爲本地數據庫操作執行得如此之快,但如果您使用長時間運行的複雜查詢連接到大型遠程數據庫,方法是值得的,因爲它爲用戶提供更平滑的用戶體驗。

+0

謝謝。我設法使用你的優秀示例來允許我的數據庫查詢在後臺運行,然後將項添加到表視圖中。不過,我還有一個問題。爲什麼當我運行一個更改標籤值的數據庫查詢時(使用label.setText(rs.getString(x));它拒絕工作嗎?我錯過了一些簡單的東西。數據放入表格視圖然後使用的可觀察列表中,但我不確定要使線程更改我的標籤的方法? - 請參閱http://pastebin.com/TXyf00xq – Uniqum 2013-02-15 10:04:00

3

使用jewelsea提供的解決方案進行管理。值得注意的是,如果在不使用列表,表格和/或可觀察列表(如果需要更新UI上的項目(如文本字段或標籤))時實施此方法,則只需在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; 
}