I'm wondering if anybody can help me with a rather annoying problem regarding creating a background thread in JavaFX! I currently have several SQL queries that add data to the UI which currently run on the JavaFX Application Thread (see example below). However when each of these queries execute it freezes the UI because it isn't running on a background thread. I've looked at various examples that use Task and sort of understand them but I cannot get them to work when doing database queries, some of which take a few seconds to run.
Here is one of the methods that executes a query:
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) {
}
}
Each processed record is added to an ObservableList which is linked to a TableView, or graph or simply sets the text on a label (depends on the query). How can I execute the query on a background thread and still leave the interface free to use and be updated from the queries
Thanks in advance
I created a sample solution for using a Task (as suggested in Alexander Kirov's comment) to access a database on a concurrently executing thread to the JavaFX application thread.
The relevant parts of the sample solution are reproduced below:
// 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;
}
}
Note that once you start doing things concurrently, your coding and your UI gets more complicated than the default mode without Tasks when everything is single threaded. For example, in my sample I disabled the button which initiates the Task so you cannot have multiple Tasks running in the background doing the same thing (this kind of processing is similar to the web world where you might disable a form post button to prevent a form being double posted). I also added an animated progress indicator to the scene while the long running database task was executing so that the user has an indication that something is going on.
Sample program output demonstrating the UI experience when a long running database operation is in progress (note the progress indicator is animating during the fetch which means the UI is responsive though the screenshot does not show this):
To compare the additional complexity and functionality of an implementation with concurrent tasks versus an implementation which executes everything on the JavaFX application thread, you can see another version of the same sample which does not use tasks. Note that in my case with a toy, local database the additional complexity of the task based application is unnecessary because the local database operations execute so quickly, but if you were connecting to a large remote database using long running complex queries, than the Task based approach is worthwhile as it provides users with a smoother UI experience.
Managed to resolve using the solution provided by jewelsea. It is worth noting that if implementing this method when not using lists, tables and/or observable lists where you need to update an item on the UI such as a text field or label then simply add the update code within Platform.runLater. Below are some code snippets that show my working solution.
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();
}
}
The only thing that doesn't appear to work with this implementation is the following, not sure why it doesn't work but it doesn't draw the graph.
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;
}
Related
I am using following code for registering and listening to Oracle database change notifications. This code is working fine when i run it as a standalone java program. It is receiving the notification from the database and printing as expected.
public class DBChangeNotification {
static final String USERNAME = "XXX";
static final String PASSWORD = "YYY";
static String URL = "jdbc:oracle:thin:#xxxx:xxxx:xxxx";
public static void main(String[] args) {
DBChangeNotification demo = new DBChangeNotification();
try {
demo.run();
} catch (SQLException mainSQLException) {
mainSQLException.printStackTrace();
}
}
public void run() throws SQLException {
OracleConnection conn = connect();
Properties prop = new Properties();
prop.setProperty(OracleConnection.DCN_NOTIFY_ROWIDS, "true");
prop.setProperty(OracleConnection.DCN_QUERY_CHANGE_NOTIFICATION, "true");
prop.setProperty(OracleConnection.DCN_BEST_EFFORT, "true");
DatabaseChangeRegistration dcr = conn.registerDatabaseChangeNotification(prop);
try {
// add the listenerr:
DCNDemoListener list = new DCNDemoListener(this);
dcr.addListener(list);
// second step: add objects in the registration:
Statement stmt = conn.createStatement();
// associate the statement with the registration:
((OracleStatement) stmt).setDatabaseChangeRegistration(dcr);
ResultSet rs = stmt.executeQuery("select * from xxxxxxxx where yyyy='zzzzz'");
while (rs.next()) {
}
String[] tableNames = dcr.getTables();
for (int i = 0; i < tableNames.length; i++) {
System.out.println(tableNames[i] + " is part of the registration.");
}
rs.close();
stmt.close();
} catch (SQLException ex) {
// if an exception occurs, we need to close the registration in order
// to interrupt the thread otherwise it will be hanging around.
if (conn != null) {
conn.unregisterDatabaseChangeNotification(dcr);
}
ex.printStackTrace();
throw ex;
} finally {
try {
// Note that we close the connection!
conn.close();
} catch (Exception innerex) {
innerex.printStackTrace();
}
}
}
/**
* Creates a connection the database.
*/
OracleConnection connect() throws SQLException {
OracleDriver dr = new OracleDriver();
Properties prop = new Properties();
prop.setProperty("user", DBChangeNotification.USERNAME);
prop.setProperty("password", DBChangeNotification.PASSWORD);
return (OracleConnection) dr.connect(DBChangeNotification.URL, prop);
}
}
/**
* DCN listener: it prints out the event details in stdout.
*/
class DCNDemoListener implements DatabaseChangeListener {
DBChangeNotification demo;
DCNDemoListener(DBChangeNotification dem) {
System.out.println("DCNDemoListener");
demo = dem;
}
#Override
public void onDatabaseChangeNotification(DatabaseChangeEvent e) {
Thread t = Thread.currentThread();
System.out.println("DCNDemoListener: got an event (" + this + " running on thread " + t + ")");
System.out.println(e.toString());
synchronized (demo) {
demo.notify();
}
}
}
My requirement is to use this feature in a web application. Web application when started in the server, has to listen to data change notifications (may be on a separate thread) and notify the application through a websocket client. I have added the following code in contextInitialized method of servlet context listener, so that it will start as soon as the application starts.
public class MyServletContextListener implements ServletContextListener {
DBChangeNotification demo;
#Override
public void contextDestroyed(ServletContextEvent arg0) {
//Notification that the servlet context is about to be shut down.
}
#Override
public void contextInitialized(ServletContextEvent arg0) {
demo = new DBChangeNotification();
try {
demo.run();
} catch (SQLException mainSQLException) {
mainSQLException.printStackTrace();
}
}
}
I did not see any notifications received by the web application when database change event occurs in the registered table. Please help me in resolving the issue. I do not know whether this is a correct approach or not.... may please suggest any alternative except continuous polling. I need to start something in the server as soon as i receive notification from database. Thank you.
It might be that you're running your code on an Oracle instance that doesn't have the Notification API available.
Check this SO for more info
In the first Jframe I have a JTable filled from database and i need to pass data of the selected jTable to another frame.
So I need to know from another JInternalFrame which row was selected in the First Jframe
public void showTableData() {
try {
Class.forName(driverName);
Connection con = DriverManager.getConnection(url, userName, password);
String sql = "SELECT t.name, t.exam, l.coursename\n"
+ "FROM exam AS t\n"
+ "INNER JOIN Course as l ON (t.LendaID=l.LendaID)";
PreparedStatement ps = con.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
int i = 0;
Jtable1.setModel(DbUtils.resultSetToTableModel(rs));
} catch (Exception ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(null, ex.getMessage(), "Error",
JOptionPane.ERROR_MESSAGE);
}
}
This is the table I've in the first Jframe
Regardless the number of your components, as a simple solution, you can create a CourseEventDispatcher class to be central point to dispatch course events across the application.
public class CourseEventDispatcher {
private List<CourseEventSubscriber> subscribers;
// ...
public void dispatchEvent(CourseEvent event) {
for(CourseEventSubscriber: subscribers) {
if( event.getSource() != subscriber ) {
subscriber.onCourseEvent(event);
}
}
}
}
And for each relevant view, there is a controller which is a CourseEventSubscriber:
public class SomeFrameController implements CourseEventSubscriber {
private CourseEventDispatcher courseEventDispatcher;
public SomeFrameController(CourseEventDispatcher courseEventDispather) {
this.courseEventDispatcher = courseEventDispatcher;
}
public void addSelectionListener() {
// ...
table.getSelectionModel().addListSelectionListener(
new ListSelectionListener() {
public void valueChanged(ListSelectionEvent event) {
doYourOwnStuff();
// then dispatch the event
courseEventDispatcher.dispatch(new CourseEvent(this, event));
}
}
);
}
// from some other view
public void onCourseEvent(CourseEvent event) {
// process the event
// e.g. event.getEvent()
}
}
And CourseEvent is a simple class of
public class CourseEvent {
private CourseEventSubscriber source;
private EventObject event;
public CourseEvent(CourseEventSubscriber source, EventObject event) {
this.source = source;
this.event = event;
}
// getters
}
You can add(register) your controllers after you created a dispatcher.
Hope this gives you another perspective.
I have 1.5 million records in my mysql table. I'm trying to read all the records in a batch process i.e,planning to read 1000 records in a batch and print those records in console.
For this I'm planning to implement multithreading concept using java. How can I implement this?
In MySQL you get all records at once or you get them one by one in a streaming fashion (see this answer). Alternatively, you can use the limit keyword for chunking (see this answer).
Whether you use streaming results or chunking, you can use multi-threading to process (or print) data while you read data. This is typically done using a producer-consumer pattern where, in this case, the producer retrieves data from the database, puts it on a queue and the consumer takes the data from the queue and processes it (e.g. print to the console).
There is a bit of administration overhead though: both producer and consumer can freeze or trip over an error and both need to be aware of this so that they do not hang forever (potentially freezing your application). This is where "reasonable" timeouts come in ("reasonable" depends entirely on what is appropriate in your situation).
I have tried to put this in a minimal running example, but it is still a lot of code (see below). There are two commented lines that can be used to test the timeout-case. There is also a refreshTestData variable that can be used to re-use inserted records (inserting records can take a long time).
To keep it clean, a lot of keywords like private/public are omitted (i.e. these need to be added in non-demo code).
import java.sql.*;
import java.util.*;
import java.util.concurrent.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FetchRows {
private static final Logger log = LoggerFactory.getLogger(FetchRows.class);
public static void main(String[] args) {
try {
new FetchRows().print();
} catch (Exception e) {
e.printStackTrace();
}
}
void print() throws Exception {
Class.forName("com.mysql.jdbc.Driver").newInstance();
Properties dbProps = new Properties();
dbProps.setProperty("user", "test");
dbProps.setProperty("password", "test");
try (Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", dbProps)) {
try (Statement st = conn.createStatement()) {
prepareTestData(st);
}
// https://stackoverflow.com/a/2448019/3080094
try (Statement st = conn.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY,
java.sql.ResultSet.CONCUR_READ_ONLY)) {
st.setFetchSize(Integer.MIN_VALUE);
fetchAndPrintTestData(st);
}
}
}
boolean refreshTestData = true;
int maxRecords = 5_555;
void prepareTestData(Statement st) throws SQLException {
int recordCount = 0;
if (refreshTestData) {
st.execute("drop table if exists fetchrecords");
st.execute("create table fetchrecords (id mediumint not null auto_increment primary key, created timestamp default current_timestamp)");
for (int i = 0; i < maxRecords; i++) {
st.addBatch("insert into fetchrecords () values ()");
if (i % 500 == 0) {
st.executeBatch();
log.debug("{} records available.", i);
}
}
st.executeBatch();
recordCount = maxRecords;
} else {
try (ResultSet rs = st.executeQuery("select count(*) from fetchrecords")) {
rs.next();
recordCount = rs.getInt(1);
}
}
log.info("{} records available for testing.", recordCount);
}
int batchSize = 1_000;
int maxBatchesInMem = 3;
int printFinishTimeoutS = 5;
void fetchAndPrintTestData(Statement st) throws SQLException, InterruptedException {
final BlockingQueue<List<FetchRecordBean>> printQueue = new LinkedBlockingQueue<List<FetchRecordBean>>(maxBatchesInMem);
final PrintToConsole printTask = new PrintToConsole(printQueue);
new Thread(printTask).start();
try (ResultSet rs = st.executeQuery("select * from fetchrecords")) {
List<FetchRecordBean> l = new LinkedList<>();
while (rs.next()) {
FetchRecordBean bean = new FetchRecordBean();
bean.setId(rs.getInt("id"));
bean.setCreated(new java.util.Date(rs.getTimestamp("created").getTime()));
l.add(bean);
if (l.size() % batchSize == 0) {
/*
* The printTask can stop itself when this producer is too slow to put records on the print-queue.
* Therefor, also check printTask.isStopping() to break the while-loop.
*/
if (printTask.isStopping()) {
throw new TimeoutException("Print task has stopped.");
}
enqueue(printQueue, l);
l = new LinkedList<>();
}
}
if (l.size() > 0) {
enqueue(printQueue, l);
}
} catch (TimeoutException | InterruptedException e) {
log.error("Unable to finish printing records to console: {}", e.getMessage());
printTask.stop();
} finally {
log.info("Reading records finished.");
if (!printTask.isStopping()) {
try {
enqueue(printQueue, Collections.<FetchRecordBean> emptyList());
} catch (Exception e) {
log.error("Unable to signal last record to print.", e);
printTask.stop();
}
}
if (!printTask.await(printFinishTimeoutS, TimeUnit.SECONDS)) {
log.error("Print to console task did not finish.");
}
}
}
int enqueueTimeoutS = 5;
// To test a slow printer, see also Thread.sleep statement in PrintToConsole.print.
// int enqueueTimeoutS = 1;
void enqueue(BlockingQueue<List<FetchRecordBean>> printQueue, List<FetchRecordBean> l) throws InterruptedException, TimeoutException {
log.debug("Adding {} records to print-queue.", l.size());
if (!printQueue.offer(l, enqueueTimeoutS, TimeUnit.SECONDS)) {
throw new TimeoutException("Unable to put print data on queue within " + enqueueTimeoutS + " seconds.");
}
}
int dequeueTimeoutS = 5;
class PrintToConsole implements Runnable {
private final BlockingQueue<List<FetchRecordBean>> q;
private final CountDownLatch finishedLock = new CountDownLatch(1);
private volatile boolean stop;
public PrintToConsole(BlockingQueue<List<FetchRecordBean>> q) {
this.q = q;
}
#Override
public void run() {
try {
while (!stop) {
List<FetchRecordBean> l = q.poll(dequeueTimeoutS, TimeUnit.SECONDS);
if (l == null) {
log.error("Unable to get print data from queue within {} seconds.", dequeueTimeoutS);
break;
}
if (l.isEmpty()) {
break;
}
print(l);
}
if (stop) {
log.error("Printing to console was stopped.");
}
} catch (Exception e) {
log.error("Unable to print records to console.", e);
} finally {
if (!stop) {
stop = true;
log.info("Printing to console finished.");
}
finishedLock.countDown();
}
}
void print(List<FetchRecordBean> l) {
log.info("Got list with {} records from print-queue.", l.size());
// To test a slow printer, see also enqueueTimeoutS.
// try { Thread.sleep(1500L); } catch (Exception ignored) {}
}
public void stop() {
stop = true;
}
public boolean isStopping() {
return stop;
}
public void await() throws InterruptedException {
finishedLock.await();
}
public boolean await(long timeout, TimeUnit tunit) throws InterruptedException {
return finishedLock.await(timeout, tunit);
}
}
class FetchRecordBean {
private int id;
private java.util.Date created;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public java.util.Date getCreated() {
return created;
}
public void setCreated(java.util.Date created) {
this.created = created;
}
}
}
Dependencies:
mysql:mysql-connector-java:5.1.38
org.slf4j:slf4j-api:1.7.20 (and to get logging shown in console: ch.qos.logback:logback-classic:1.1.7 with ch.qos.logback:logback-core:1.1.7)
I'm wondering if anybody can help me with a rather annoying problem regarding creating a background thread in JavaFX! I currently have several SQL queries that add data to the UI which currently run on the JavaFX Application Thread (see example below). However when each of these queries execute it freezes the UI because it isn't running on a background thread. I've looked at various examples that use Task and sort of understand them but I cannot get them to work when doing database queries, some of which take a few seconds to run.
Here is one of the methods that executes a query:
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) {
}
}
Each processed record is added to an ObservableList which is linked to a TableView, or graph or simply sets the text on a label (depends on the query). How can I execute the query on a background thread and still leave the interface free to use and be updated from the queries
Thanks in advance
I created a sample solution for using a Task (as suggested in Alexander Kirov's comment) to access a database on a concurrently executing thread to the JavaFX application thread.
The relevant parts of the sample solution are reproduced below:
// 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;
}
}
Note that once you start doing things concurrently, your coding and your UI gets more complicated than the default mode without Tasks when everything is single threaded. For example, in my sample I disabled the button which initiates the Task so you cannot have multiple Tasks running in the background doing the same thing (this kind of processing is similar to the web world where you might disable a form post button to prevent a form being double posted). I also added an animated progress indicator to the scene while the long running database task was executing so that the user has an indication that something is going on.
Sample program output demonstrating the UI experience when a long running database operation is in progress (note the progress indicator is animating during the fetch which means the UI is responsive though the screenshot does not show this):
To compare the additional complexity and functionality of an implementation with concurrent tasks versus an implementation which executes everything on the JavaFX application thread, you can see another version of the same sample which does not use tasks. Note that in my case with a toy, local database the additional complexity of the task based application is unnecessary because the local database operations execute so quickly, but if you were connecting to a large remote database using long running complex queries, than the Task based approach is worthwhile as it provides users with a smoother UI experience.
Managed to resolve using the solution provided by jewelsea. It is worth noting that if implementing this method when not using lists, tables and/or observable lists where you need to update an item on the UI such as a text field or label then simply add the update code within Platform.runLater. Below are some code snippets that show my working solution.
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();
}
}
The only thing that doesn't appear to work with this implementation is the following, not sure why it doesn't work but it doesn't draw the graph.
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;
}
Can anyone show me a way to force one task in java to complete before the next task is allows to start? Specifically, I want to edit the code below so that the first marked two lines of code are completely finished before the next marked two lines are called.
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String idString = req.getParameter("id");
Long id = new Long(idString);
//complete the actions specified on next two lines
School school = new SchoolDAO().findSchool(id);
req.setAttribute("school", school);
//before even starting the actions specified on the next two lines
List<CourseSummary> coursesummaries = new CourseSummaryDAO().findAllcsum(id);
req.setAttribute("coursesummaries", coursesummaries);
jsp.forward(req, resp);
}
EDIT:
To better understand Fernando's suggestion, I am including some relevant parts of SchoolDAO as follows:
public class SchoolDAO extends DataAccessObject{
public School findSchool(Long id) {
ResultSet rs = null;
PreparedStatement statement = null;
Connection connection = null;
try {
connection = getConnection();
String sql = "select * from schoolprog where id=?";
statement = connection.prepareStatement(sql);
statement.setLong(1, id.longValue());
rs = statement.executeQuery();
if (!rs.next()) {return null;}
return readSchool(rs);
}
catch (SQLException e) {throw new RuntimeException(e);}
finally {close(rs, statement, connection);}
}
private School readSchool(ResultSet rs) throws SQLException {
Long id = new Long(rs.getLong("id"));
String spname = rs.getString("spname");
String spurl = rs.getString("spurl");
School school = new School();
school.setId(id);
school.setName(spname);
school.setUrl(spurl);
return school;
}
}
Similarly, CourseSummaryDAO contains:
public class CourseSummaryDAO extends DataAccessObject{
public List<CourseSummary> findAllcsum(Long sid) {
LinkedList<CourseSummary> coursesummaries = new LinkedList<CourseSummary>();
ResultSet rs = null;
PreparedStatement statement = null;
Connection connection = null;
try {
connection = getConnection(); //this is the line throwing null pointer error
String sql = "select * from coursetotals where spid=?";
statement = connection.prepareStatement(sql);
statement.setLong(1, sid);
rs = statement.executeQuery();
//for every row, call read method to extract column
//values and place them in a coursesummary instance
while (rs.next()) {
CourseSummary coursesummary = readcsum("findAll", rs);
coursesummaries.add(coursesummary);
}
return coursesummaries;
}
catch (SQLException e) {throw new RuntimeException(e);}
finally {close(rs, statement, connection);}
}
The line where the program is breaking is:
connection = getConnection(); //
If you have two tasks that should be performed serially (i.e. one finishes before the next one starts) then the best answer is to perform them synchronously. For instance, suppose that task1() and task2() are the tasks:
// Wrong way:
Runnable r1 = new Runnable(){
public void run() {
task1();
}};
Runnable r2 = new Runnable(){
public void run() {
// Wait for r1 to finish task1 ... somehow
task2();
}};
// Right way:
Runnable r = new Runnable(){
public void run() {
task1();
task2();
}};
And in your case, it looks like the doGet call can only return when it gets the result of both tasks. So that suggests that you shouldn't be using threads at all in this case. Just call task1() and task2() in sequence ... on the request thread.
EDIT
Looking at the doGet method and the two classes that you added subsequently, it looks like the processing is already sequential / serial. That is, the first "task" ends before the second "task" starts.
The problem with getConnection() throwing NullPointerException is (most likely) nothing to do with asynchrony. However I can't be sure of that without seeing the code of getConnection() and the complete stacktrace.
In Java, everything is normally executed in order, meaning that a given line of code will completely finish executing before the next line will start to do anything. The exception to this rule is when threads come into play. Threads allow multiple blocks of code to execute simultaneously. Because you aren't using any threads in your program (you'd know if you were, don't worry), it's guaranteed that the first two lines of code will complete before the next two begin to be executed.
So, your problem doesn't seem to be that your code is running "out of order". It's likely that your error is somewhere within the getConnection() method if that's what's throwing the NPE.
Here's an example (see Java Threads waiting value for details)
import java.util.concurrent.CountDownLatch;
class MyTask implements Runnable
{
CountDownLatch signal;
public MyTask(CountDownLatch signal)
{
this.signal = signal;
}
public void run()
{
System.out.println("starting task");
for (int i = 0; i < 10000000; i++)
Math.random();
//call when the task is done
signal.countDown();
}
}
public class Program
{
public static void main(String[] args) {
int workers = 1;
CountDownLatch signal = new CountDownLatch(workers);
new Thread(new MyTask(signal)).start();
try {
// Waits for all the works to finish ( only 1 in this case)
signal.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task is done");
}
}
This is just a suggestion, maybe there's a better design:
import java.util.concurrent.CountDownLatch;
public class SchoolDAO extends DataAccessObject implements Runnable {
Long id;
CountDownLatch signal;
School searchResult;
public SchoolDAO(Long id, CountDownLatch signal)
{
this.id = id;
this.signal = signal;
}
public void run()
{
searchResult = findSchool(id);
signal.countDown();
}
// the other methods didn't change
}
Now you can call it inside doGet():
CountDownLatch signal = new CountDownLatch(1);
SchoolDAO dao = new SchoolDAO(id, signal);
new Thread(dao).start();
try {
signal.await();
} catch (InterruptedException e)
{
e.printStackTrace();
}
School result = dao.searchResult;