2013-02-14 60 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; 
}