2013-02-14 10 views
10

Ich frage mich, ob jemand mir mit einem ziemlich nervigen Problem in Bezug auf die Erstellung eines Hintergrund-Threads in JavaFX helfen kann! Ich habe derzeit mehrere SQL-Abfragen, die Daten zur Benutzeroberfläche hinzufügen, die derzeit auf dem JavaFX-Anwendungsthread ausgeführt werden (siehe Beispiel unten). Wenn jedoch jede dieser Abfragen ausgeführt wird, wird die Benutzeroberfläche eingefroren, da sie nicht auf einem Hintergrundthread ausgeführt wird. Ich habe mir verschiedene Beispiele angeschaut, die Task verwenden und sie verstehen, aber ich kann sie nicht zum Laufen bringen, wenn Datenbankabfragen durchgeführt werden, von denen einige einige Sekunden dauern.JavaFX - Hintergrund Thread für SQL-Abfrage

Hier ist eine der Methoden, die eine Abfrage ausführt:

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) { 
    } 
} 

jeder verarbeitete Datensatz zu einem ObservableList hinzugefügt wird, die zu einem Tableview verknüpft ist, oder ein Diagramm oder setzen einfach den Text auf einem Etikett (abhängig von die Abfrage). Wie kann ich die Abfrage auf einem Hintergrund-Thread ausführen und lassen noch die Schnittstelle frei zu verwenden und aus den Abfragen

Vielen Dank im Voraus aktualisiert

+1

Haben Sie Klassen im Paket javafx.concurrent beobachtet? docs.oracle.com/javafx/2/api/javafx/concurrent/.... Ich denke, eine Task-Klasse sollte für Sie besonders interessant sein: docs.oracle.com/javafx/2/api/javafx/concurrent/Task.html, Sie können ihr Javadoc lesen, um die vollständige Liste der Optionen zu verstehen. –

+0

Vielleicht kann DataFX Ihnen helfen: http://www.javafxdata.org oder http://www.guigarage.com/category/datafx/ –

Antwort

14

ich für die Verwendung eines Task ein sample solution erstellt (wie in Alexander Kirov Kommentar vorgeschlagen) um auf eine Datenbank eines gleichzeitig laufenden Threads für den JavaFX-Anwendungsthread zuzugreifen.

die relevanten Teile der Probenlösung sind nachstehend wiedergegeben:

// 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; 
    } 
}  

Beachten Sie, dass, wenn Sie die Dinge beginnen gleichzeitig zu tun, Ihre Codierung und die Benutzeroberfläche wird komplizierter als der Standardmodus ohne Aufgaben, wenn alles Gewindeeinzel ist . In meinem Beispiel habe ich beispielsweise die Schaltfläche deaktiviert, die die Aufgabe initiiert, so dass nicht mehrere Aufgaben im Hintergrund ausgeführt werden können, die dasselbe tun (diese Art der Verarbeitung ähnelt der Web-Welt, in der Sie möglicherweise eine Formular-Schaltfläche deaktivieren) Formular wird doppelt gebucht). Ich fügte der Szene auch einen animierten Fortschrittsanzeiger hinzu, während der lang laufende Datenbank-Task ausgeführt wurde, so dass der Benutzer anzeigt, dass etwas vor sich geht.

Beispielprogramm Ausgabe der UI-Erfahrung zeigt, wenn eine lange laufenden Datenbankbetrieb läuft (beachten Sie die Statusanzeige wird während der Animieren holen, was bedeutet, die Benutzeroberfläche reagiert, obwohl der Screenshot dies nicht zeigen):

databasefetcher

Um die zusätzliche Komplexität und Funktionalität einer Implementierung mit konkurrierenden Aufgaben gegenüber einer Implementierung zu vergleichen, die alles im JavaFX-Anwendungsthread ausführt, können Sie another version of the same sample which does not use tasks sehen. Beachten Sie, dass in meinem Fall mit einer lokalen Datenbank für Spielzeug die zusätzliche Komplexität der aufgabenbasierten Anwendung unnötig ist, da die lokalen Datenbankoperationen so schnell ausgeführt werden, aber wenn Sie eine Verbindung zu einer großen entfernten Datenbank über lange laufende komplexe Abfragen herstellen Ansatz ist sinnvoll, da es Benutzern eine reibungslosere UI-Erfahrung bietet.

+0

Danke. Es ist mir gelungen, Ihr hervorragendes Beispiel zu verwenden, um zu ermöglichen, dass meine Datenbankabfrage im Hintergrund ausgeführt wird und Sie dann Elemente in eine Tabellenansicht einfügen. Ich habe jedoch eine andere Frage. Warum, wenn ich eine Datenbankabfrage ausführe, die die Werte von Labels ändert (mit label.setText (rs.getString (x)); weigert es sich zu arbeiten? Fehle ich etwas einfaches. Ich nehme an, dass die Tabelle funktioniert, weil es einfach hinzufügt Daten in eine Observable List, die die Tabellenansicht dann verwendet, aber ich bin mir nicht sicher, auf welche Art und Weise der Thread meine Labels ändern soll - siehe http://pastebin.com/TXyf00xq – Uniqum

3

Verwaltet, um mit der von jewelsea bereitgestellten Lösung zu lösen. Beachten Sie, dass Sie bei der Implementierung dieser Methode, wenn Sie keine Listen, Tabellen und/oder beobachtbaren Listen verwenden, bei denen Sie ein Element auf der Benutzeroberfläche aktualisieren müssen, z. B. ein Textfeld oder ein Label, den Aktualisierungscode einfach in Platform.runLater hinzufügen. Im Folgenden finden Sie einige Codefragmente, die meine Arbeitslösung zeigen.

Code:

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(); 
    } 
} 

Das einzige, was nicht erscheint bei dieser Implementierung zu arbeiten ist die folgende, nicht sicher, warum es nicht funktioniert, aber es funktioniert nicht, die Grafik zeichnen.

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; 
}