I implement my MapStore to save entries to database (external datastore), but there a issue of starting new node when old member storing entries to database. Firstly, cluster A has only Member1.
Client puts 20K entries to the cluster then Member1 starts saving entries by batch to database (write-behind mode). When 5K entries has been saved to database (not finish whole 20K entries) I start Member2 to join the cluster A
--> Member2 received about 10K entries (result of repartition) , Member2 stores them to the database, despite of this 10K entries has been saved to database by Member1
This leads to duplicate 10K records in database (10K from Member2). This is not occurs when 2 members are both ready when start putting entries from client, or in write-though mode
Please tell me why? This is my code
/**
*
* #author Mina Mimi
*/
public class SmsLogMapStore implements MapStore<String, SmsLog>, MapLoaderLifecycleSupport {
Connection conn;
static final Logger logger = Logger.getLogger(SmsLogMapStore.class.getSimpleName());
#Override
public synchronized void store(String k, SmsLog v) {
logger.info("===write one >>>:" + v.getContent());
String sql = "insert into smslog (content) values (?)";
PreparedStatement st = null;
try {
st = conn.prepareStatement(sql);
st.setString(1, v.getContent());
st.executeUpdate();
} catch (SQLException ex) {
Logger.getLogger(SmsLogMapStore.class.getName()).log(Level.SEVERE, null, ex);
} finally {
try {
st.close();
} catch (SQLException ex) {
Logger.getLogger(SmsLogMapStore.class.getName()).log(Level.SEVERE, null, ex);
}
}
logger.info("Finished write one >>>");
}
#Override
public synchronized void storeAll(Map<String, SmsLog> map) {
logger.info("Write batch:" + map.size());
String sql = "insert into smslog (content) values (?)";
PreparedStatement st = null;
try {
st = conn.prepareStatement(sql);
for (Map.Entry<String, SmsLog> entry : map.entrySet()) {
String key = entry.getKey();
SmsLog v = entry.getValue();
logger.info("===Writing(k,v):" + key + "," + v + " from batch");
st.setString(1, v.getContent());
st.addBatch();
}
st.executeBatch();
} catch (SQLException ex) {
Logger.getLogger(SmsLogMapStore.class.getName()).log(Level.SEVERE, null, ex);
} finally {
try {
st.close();
} catch (SQLException ex) {
Logger.getLogger(SmsLogMapStore.class.getName()).log(Level.SEVERE, null, ex);
}
}
logger.info("Finished Write batch >>>");
}
#Override
public void delete(String k) {
return;
}
#Override
public void deleteAll(Collection<String> clctn) {
//throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
for (String key : clctn) {
delete(key);
}
}
#Override
public SmsLog load(String k) {
return new SmsLog();
}
#Override
public Map<String, SmsLog> loadAll(Collection<String> clctn) {
logger.info("############loadAll");
HashMap<String, SmsLog> result = new HashMap<String, SmsLog>();
return result;
}
#Override
public synchronized Iterable<String> loadAllKeys() {
List<String> keys = new LinkedList<String>();
return keys;
}
#Override
public void init(HazelcastInstance hi, Properties prprts, String string) {
try {
conn = getConnection();
} catch (SQLException ex) {
Logger.getLogger(SmsLogMapStore.class.getName()).log(Level.SEVERE, null, ex);
}
}
#Override
public void destroy() {
try {
conn.close();
} catch (SQLException ex) {
Logger.getLogger(SmsLogMapStore.class.getName()).log(Level.SEVERE, null, ex);
}
}
private Connection getConnection() throws SQLException {
Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test_imdb", "root", "");
return conn;
}
}
Since storeAll() doesn’t signal the progress back to Hazelcast, Hazelcast has no means of restoring the write operation from where it left off. It must simply restart it from the scratch on the new set of cluster members.
As such, the storeAll() implementation should be made idempotent. E.g. it must be ready to survive multiple invocations on the same keys. The simplest approach is to use a UPSERT-style operation to write data to the storage. If the record was already written, the DB will just ignore it (or rewrite it with the same value).
If you need to store big batches of data, consider using the data pipeline. Jet can resume from a specific source offset after a failure or a topology change, the data operation isn't just restarted. It will be a solution with more moving parts, so don’t use it unless it’s really a concern. 20k keys is kind of tiny dataset.
What is the storage system you are using? Does it support upserts by any means?
There is no distributed coordination for write-behind. Means adding or removing nodes can cause data loss or duplications, no guarantee here, current implementation is working as a best effort manner.
If you ask for a workaround, you can have a distributed id supplier and before putting objects in IMap, you can set an id to it from this supplier, when map-store is saving the object, duplications can be detected by the help of this id.
Related
Can someone please provide example code on how to use the Bluetooth cn1 library to read and write text data? I tried looking through the project source code (https://github.com/chen-fishbein/bluetoothle-codenameone) for example code, but could not find any that reads/writes text data using the appropriate methods. Those methods also don't have any Java docs.
Here's the code I'm using to send:
public void sendMessage(String message) {
if (Display.getInstance().isSimulator()) {
System.out.println(message);
} else {
// System.out.println("Sending message: " + message);
String b64WriteString = Base64.encode(message.getBytes());
try {
bt.write(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
}
}, deviceAddress, services.get(0), sendDataCharacteristic, b64WriteString, false);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
And here to receive:
private void registerNotifications() {
System.out.print("Registering notifications...");
try {
bt.subscribe(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
JSONObject dataIncoming = (JSONObject) evt.getSource();
String base64Value = "";
try {
if (dataIncoming.getString("status").equals("subscribedResult")) {
base64Value = dataIncoming.getString("value");
}
} catch (JSONException e) {
e.printStackTrace();
}
String message = new String(Base64.decode(base64Value.getBytes()));
Display.getInstance().callSerially(new Runnable() {
#Override
public void run() {
messageReceived.set(message);
}
});
}
}, deviceAddress, services.get(0), receiveDataCharacteristic);
System.out.println("Registered");
System.out.println("Starting communication");
} catch (IOException e) {
System.err.println("Unable to register notifications " + e.getMessage());
e.printStackTrace();
}
}
I have fields defined for the service and characteristic UUID's. Also, the callSerially might not be needed anymore. I think I recall that the CN1LIB was updated to do that, but I don't remember for certain.
For that device, the service characteristic is "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
The sendCharacteristic is "0x0002"
The receiveCharacteristic is "0x0003"
After taking the suggestions of James H, and some more trial an error, I finally manage to get data transfer between the Adafruit's Bluefruit LE Friend working consistently, at least on an Android device. Not sure about iOS though, since I haven't tested it. Here are the critical code pieces needed.
First, you need the Service, TX and RX Characteristics UUIDs. These UUIDs were found here. Note, these don't need to be upper case.
public static final String UUID_SERVICE = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
public static final String UUID_RX = "6e400003-b5a3-f393-e0a9-e50e24dcca9e";
public static final String UUID_TX = "6e400002-b5a3-f393-e0a9-e50e24dcca9e";
Next, once you scanned and found the devices, call the connect() method to make the actual connection, and critically call the discover() method. Once the discover() callback gets called, then add the "subscriber" to receive data.
private void connect(String address) {
bleAddress = address; // set the global BLE address
if (!connected) {
// start an infinite progress dialog
final Dialog ip = new InfiniteProgress().showInifiniteBlocking();
try {
bt.connect(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
ip.dispose();
Object obj = evt.getSource();
print("Connected to Bluetooth LE device ...\n" + obj, true);
// must be called on Android devices in order to load on the UUIDs, otherwise there is an error that service can't be found. Won't do anything on ios though?
discover();
connected = true;
}
}, address);
} catch (IOException ex) {
ip.dispose();
String message = "Error connecting to bluetooth device: " + address;
print(message + "\n" + ex.getMessage(), false);
}
} else {
String message = "BLE device already connected to: " + address;
print(message, false);
}
}
private void discover() {
try {
bt.discover(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
print("BLE Information Received ...", true);
addSubscriber();
}
}, bleAddress);
} catch (Exception ex) {
print(ex.getMessage(), false);
}
// if we running on is add the subscriber here since the above bt call
// does nothing?
if (Display.getInstance().getPlatformName().equals("ios")) {
print("Adding subscriber for iOS Device", true);
addSubscriber();
}
}
private void addSubscriber() {
try {
bt.subscribe(new ActionListener() {
StringBuilder sb = new StringBuilder();
#Override
public void actionPerformed(ActionEvent evt) {
JSONObject dataIncoming = (JSONObject) evt.getSource();
String base64Value = "";
try {
if (dataIncoming.getString("status").equals("subscribedResult")) {
base64Value = dataIncoming.getString("value");
}
} catch (JSONException e) {
console.setText("Error reading data: " + e.getMessage());
}
String message = new String(Base64.decode(base64Value.getBytes()));
sb.append(message);
if (message.endsWith("\r\n")) {
processData(sb.toString());
sb = new StringBuilder();
}
}
}, bleAddress, UUID_SERVICE, UUID_RX);
String message = console.getText() + "\nSubcriber added ...";
console.setText(message);
} catch (IOException ex) {
String message = "Error Subscribing: " + ex.getMessage();
console.setText(message);
}
}
So this sets up the connection, discovers the services, and finally adds the subscriber method which receives the data, and critically uses a buffer to collect the received data until the CRLF characters are received.
However, another major issue I ran into was the default 23 byte send limit (maybe an Android only issue?) of the BLE specification. If you tried sending more than this, the connection just gets dropped with no meaningful error message being returned. To get around this, I used the technique suggested here, which entails splitting data into chunks of 20 byte arrays. Since we sending regular ASCII text, then 20 characters should be 20 bytes, so I just split the text into Strings 20 characters long. Not the most efficient by it works and it easier to debug.
private void sendText(final String data) {
try {
String b64String = Base64.encode(data.getBytes());
bt.write(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
if(data.endsWith("\r\n")) {
print("Data sent ...", true);
}
}
}, bleAddress, UUID_SERVICE, UUID_TX, b64String, false);
} catch (IOException ex) {
String message = "Error sending: " + data + "\n"
+ UUID_SERVICE + "\n"
+ UUID_TX + "\n"
+ ex.getMessage();
print(message, false);
}
}
private void splitAndSend(String text) {
text += "\r\n";
// first split data in chunk size of 20 chracters
ArrayList<String> sl = new ArrayList<>();
char[] data = text.toCharArray();
int len = data.length;
int chunkSize = 20;
for (int i=0; i < len; i+= chunkSize) {
sl.add(new String(data, i, Math.min(chunkSize,len - i)));
}
// now send chunks amd wait 100 ms to prevent any erros
for(String word: sl) {
sendText(word);
try {
Thread.sleep(100);
} catch (InterruptedException ex) {}
}
}
The complete source code with GUI stuff can be found here, but this is definitely a work in progress.
I have a pretty simplistic JavaFX application. In it, I have a Java object for handling database activities, mainly executing queries. To prevent my UI from completely freezing while the query executes, I've implemented a background thread using the javafx.concurrent.Service. This works great on my connect method, which doesn't return anything. However, in my query method it immediately jumps to the return line, and of course returns null. Then it goes back and runs the query, but it's already returned an empty arraylist.
What am I doing wrong?
Here's my method:
public ArrayList<Foo> runQuery() throws SQLException {
ArrayList<Foo> result = new ArrayList<Foo>();
backgroundThread = new Service<Void>() {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
protected Void call() throws Exception {
stmt = conn.createStatement();
String query = "Select stuff...
rs = stmt.executeQuery(query);
return null;
}
};
}
};
backgroundThread.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent argo) {
try {
while (rs.next()) {
result.add(new Foo(rs.getString(1)));
}
} catch (SQLException e) {
e.printStackTrace();
}
controller.addLogEntry("done.\n");
}
});
backgroundThread.restart();
return result;
}
I am new to JavaFx/Concurrency so I read the tutorial over at Concurrency in JavaFX but I am still a little confused about the implementation of background threads in a JavaFX Gui.
I'm trying to write a small GUI that interfaces with some serial devices (using JSSC-2.8) and that updates the GUI based on the responses from those devices. But, there's a lag between when the message is written and when the device responds, and using Thread.sleep() for an arbitrary amount of time wasn't a reliable way for me program it. So instead I want to use wait() and notify() methods from the concurrency package (with all the appropriate synchronizations), but I am not sure how to implement it. What I initially did is create another Thread, inside the Task, that would write the messages and wait for the responses, and using some bindings, would update the GUI. I've included my code at the end. Here is a short form of the pseudocode I am trying to implement:
start Task:
connect to serial devices
synchronized loop:
send messages
wait() for event to fire
notify()
But what's been happening is, as soon as I call the wait(), the entire application idles and then when notify() is called (after the response fires and event), it doesn't continue where it left off in the recipe() loop, or the startTdk() loop for that matter, it's just idle. Have I implements the threads wrong? When I am calling the wait(), is it a possibility that I cause the EventDispatch or JavaFX Application Thread to pause?
I hope the question is clear, if there are any clarifications needed I can update the post.
public class OmicronRecipe extends Service<String> implements Runnable{
private final String SEPERATOR=";";
private final Tdk tdk;
private final Pvci pvci;
private final SimpleStringProperty data = new SimpleStringProperty("");
private final Float MAX_V = 26.0f,UHV=1e-8f;
private boolean isTdkOn=false, isPvciOn=false;
private String power;
private Float temp,press,maxT, setT;
private int diffMaxT,diffP,diffPow, diffT, index=0;
public OmicronRecipe(){
tdk = new Tdk("COM4");
pvci = new Pvci("COM5");
}
private synchronized void recipe(){
while (true){
try {
sendMessages();
data.set(power+SEPERATOR+temp+SEPERATOR+press);
calcDiffs();
if (diffPow < 0){
if(diffMaxT < 0){
if(diffT < 0){
if (diffP < 0){
if(!rampPow()){
//Max Power reached
}
}else{
//Wait for pressure drop
}
}
}else{
//Wait until quit
}
}else{
//Max power reached
}
Thread.sleep(5000);
} catch (InterruptedException ex) {
Logger.getLogger(OmicronRecipe.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
private synchronized boolean rampPow(){
boolean isRamped=false;
Float setPow = tdk.getSetPow(index), curPow;
setT = tdk.getSetT(index);
curPow = Float.parseFloat(power);
if(curPow.compareTo(setPow) < 0){
do{
curPow += 0.1f;
tdk.sendMessage("PV "+curPow+"\r");
try {
wait();
} catch (InterruptedException ex) {
Logger.getLogger(OmicronRecipe.class.getName()).log(Level.SEVERE, null, ex);
}
curPow = Float.parseFloat(power);
}while(curPow.compareTo(setPow) < 0);
index++;
isRamped=true;
}
return isRamped;
}
public synchronized boolean connect(){
if(!isTdkOn && !isPvciOn){
isTdkOn = tdk.connect();
isPvciOn = pvci.connect();
}
return isTdkOn && isPvciOn;
}
public synchronized boolean disconnect(){
if(tdk!=null && pvci !=null){
isTdkOn = tdk.disconnect();
isPvciOn = pvci.disconnect();
}
return !isTdkOn && !isPvciOn;
}
public synchronized StringProperty getData(){
return data;
}
public void setMaxT(Float maxT){
this.maxT = maxT;
}
private synchronized void calcDiffs(){
Float pow = Float.parseFloat(power);
diffPow = pow.compareTo(MAX_V);
diffMaxT = temp.compareTo(maxT);
diffT = temp.compareTo(100f);
diffP = press.compareTo(UHV);
}
private synchronized void setListeners(){
tdk.getLine().addListener((ov,t, t1)-> {
synchronized (this){
System.out.println("New Power: "+t1);
power = t1;
this.notify();
}
});
pvci.getLine().addListener((ov,t,t1) ->{
synchronized (this){
String[] msg = t1.split(SEPERATOR);
if(msg.length == 2){
switch(msg[0]){
case "temperature":
System.out.println("Temperaute");
temp = Float.parseFloat(msg[1]);
break;
case "pressure":
System.out.println("Pressure");
press = Float.parseFloat(msg[1]);
break;
default:
System.out.println("Nothing; Something went wrong");
break;
}
}
this.notify();
}
});
}
private synchronized void sendMessages(){
try {
tdk.sendMessage("PV?\r");
this.wait();
pvci.sendMessage("temperature");
this.wait();
pvci.sendMessage("pressure");
this.wait();
} catch (InterruptedException ex) {
Logger.getLogger(OmicronRecipe.class.getName()).log(Level.SEVERE, null, ex);
}
}
private synchronized boolean startTdk(){
boolean isOut=false;
if(isTdkOn){
try {
tdk.sendMessage("ADR 06\r");
this.wait();
System.out.println("Power: "+power);
if(power.equals("OK")){
tdk.sendMessage("OUT?\r");
this.wait();
if(power.equals("OFF")){
tdk.sendMessage("OUT ON\r");
this.wait();
isOut = power.equals("ON");
}
else{
isOut = power.equals("ON");
}
}
} catch (InterruptedException ex) {
Logger.getLogger(OmicronRecipe.class.getName()).log(Level.SEVERE, null, ex);
}
}
return isOut;
}
#Override
protected Task<String> createTask() {
return new Task<String>() {
#Override
protected String call() throws IOException{
new Thread(new OmicronRecipe()).start();
return "";
}
};
}
#Override
public void run() {
if (connect()){
setListeners();
if(startTdk()){
recipe();
}
}
}
}
I won't include the Pvci class, because it just a copy of the Tdk class but with specific message sequences to talk with that machine.
public class Tdk {
private SerialPort tdkPort;
private final String portName;
private StringBuilder sb = new StringBuilder("");;
private final StringProperty line = new SimpleStringProperty("");
private final HashMap<Float,Float> calibMap;
private ArrayList<Float> list ;
private boolean isEnd=false;
public Tdk(String portName){
this.portName = portName;
System.out.println("TDK at "+portName);
calibMap = new HashMap();
setMap();
}
public synchronized boolean connect(){
tdkPort = new SerialPort(portName);
try {
System.out.println("Connecting");
tdkPort.openPort();
tdkPort.setParams(9600,
SerialPort.DATABITS_8,
SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
tdkPort.setEventsMask(SerialPort.MASK_RXCHAR);
tdkPort.addEventListener(event -> {
if(event.isRXCHAR()){
if(event.getPortName().equals(portName)){
try {
if(!isEnd){
int[] str = tdkPort.readIntArray();
if(str!=null)
hexToString(str);
}
if(isEnd){
System.out.println("Here: "+sb.toString());
isEnd=false;
String d = sb.toString();
sb = new StringBuilder("");
line.setValue(d);
}
} catch (SerialPortException e) {
Logger.getLogger(Tdk.class.getName()).log(Level.SEVERE, null, e);
}
}
}
});
} catch (SerialPortException e) {
Logger.getLogger(Tdk.class.getName()).log(Level.SEVERE, null, e);
}
return tdkPort !=null && tdkPort.isOpened();
}
public synchronized boolean disconnect(){
if(tdkPort!=null) {
try {
tdkPort.removeEventListener();
if (tdkPort.isOpened())
tdkPort.closePort();
} catch (SerialPortException e) {
Logger.getLogger(Tdk.class.getName()).log(Level.SEVERE, null, e);
}
System.out.println("Disconnecting");
}
return tdkPort.isOpened();
}
public synchronized void sendMessage(String message){
try {
tdkPort.writeBytes(message.getBytes());
} catch (SerialPortException e) {
Logger.getLogger(Tdk.class.getName()).log(Level.SEVERE, null, e);
}
}
private void setMap(){
calibMap.put(1.0f, 25.0f);
calibMap.put(7.0f, 125.0f);
calibMap.put(9.8f, 220.0f);
list = new ArrayList(calibMap.keySet());
}
public Float getSetPow(int index){
return list.get(index);
}
public Float getSetT(int index){
return calibMap.get(list.get(index));
}
public synchronized StringProperty getLine(){
return line;
}
private synchronized void hexToString(int[] hexVal){
for(int i : hexVal){
if(i != 13){
sb.append((char)i);
}else{
isEnd=true;
}
}
System.out.println("Turning: "+Arrays.toString(hexVal)+" to String: "+sb.toString()+" End: "+isEnd);
}
Freeze
Your UI freezes most probably because you are waiting on the FX Apllication Thread, to solve this there are different approaches:
JavaFX Application Thread
You can delegate some work to the FX Application Thread, therefore see Platform.runLater
Not everything can be run on this thread, but for example, in your DeviceController, you can wait until the message appears and then call Platform.runLater() and update the field (you should therefor oc hand the field over to the controller).
DataBinding
What you are describing can also be realised with DataBinding.
With this you could define a SimpleStringProperty, which is bound to your UI Label (.bind() Method). If the controller must fire its message you can set the StringProperty and the UI will update itself.
The scenario you described could be used like this:
start Task:
connect to serial devices
synchronized loop:
send messages
wait() for event to fire
**updateDate the DataBounded fields**
We are taught that, Concurrency notify/wait
Concurrency on level wait()/notify() is very low level. You should try to work with higher level synchronisation methods or helpers (where people have already solved your problems :))
So I load some data from Database and use SwingWorker for it.
public class LoadFromDatabase extends SwingWorker<ArrayList<Ucet>, GuiUpdate>{
private ArrayList<Ucet> ucty;
private JLabel lblStav;
private File dbPath;
private JProgressBar progress;
private int pocetUctov;
private JButton btnLoad;
private JButton btnStart;
public LoadFromDatabase(ArrayList<Ucet> ucty,JLabel lblStav,File dbpath,JProgressBar progress, JButton btnLoad,JButton btnStart){
this.ucty=ucty;
this.lblStav=lblStav;
this.dbPath=dbpath;
this.progress=progress;
this.btnLoad=btnLoad;
this.btnStart=btnStart;
}
#Override
protected ArrayList<Ucet> doInBackground() throws Exception {
String sqlLoadUcty="SELECT email,password FROM members";
ArrayList<Ucet> ucty2=new ArrayList<>();
try {
Class.forName("org.sqlite.JDBC");
Connection conn = DriverManager.getConnection("jdbc:sqlite:"+dbPath.getPath());
Statement stmt = conn.createStatement();
stmt.setQueryTimeout(30);
ResultSet rs = stmt.executeQuery(sqlLoadUcty);
GuiUpdate gd=new GuiUpdate(GuiUpdate.GuiType.setStartLoading);
gd.setValue(0);
publish(gd);
pocetUctov=rs.getFetchSize();
gd=new GuiUpdate(GuiUpdate.GuiType.setMaxValue);
gd.setValue(pocetUctov);
publish(gd);
int counter=0;
while (rs.next()){
Ucet uct=new Ucet(
rs.getString("email"),
rs.getString("password")
);
gd=new GuiUpdate(GuiUpdate.GuiType.setValue);
gd.setValue(counter);
publish(gd);
ucty2.add(uct);
}
rs.close();
stmt.close();
conn.close();
} catch (ClassNotFoundException ex) {
System.out.println("Problem= "+ex);
} catch (SQLException ex) {
System.out.println("Problem= "+ex);
}
return ucty2;
}
#Override
public void process(List<GuiUpdate> update){
for (GuiUpdate guiUpdate : update) {
if (guiUpdate.getToDo()==GuiUpdate.GuiType.setStartLoading) {
lblStav.setText("Loading ...");
progress.setVisible(true);
} else if (guiUpdate.getToDo()==GuiUpdate.GuiType.setMaxValue) {
progress.setMaximum(guiUpdate.getValue());
pocetUctov=guiUpdate.getValue();
progress.setMinimum(0);
} else if (guiUpdate.getToDo()==GuiUpdate.GuiType.setValue) {
progress.setValue(guiUpdate.getValue());
}
}
}
#Override
public void done(){
progress.setVisible(false);
btnLoad.setEnabled(true);
try {
ucty=get();
} catch (InterruptedException ex) {
System.out.println("Problem= "+ex);
} catch (ExecutionException ex) {
System.out.println("Problem= "+ex);
}
if (ucty!=null && ucty.size()>0) {
btnStart.setEnabled(true);
lblStav.setText("Loaded "+ucty.size()+" accounts.");
}
}
}
This is whole SwingWorker. I basicly load data from DB and update progress bar.
Once loaded then done() method is called where reference to created ArrayList in doInbackground is set to arraylist which comes from mainGUI and some buttons are allowed and disallowed.
This is how I call SwingWorker from Gui:
private void jButton4ActionPerformed(java.awt.event.ActionEvent evt) {
JFileChooser fileDb=new JFileChooser();
int returnVal=fileDb.showOpenDialog(this);
if (returnVal==JFileChooser.APPROVE_OPTION) {
databasePath=fileDb.getSelectedFile();
jButton4.setEnabled(false);
execurtor.execute(new LoadFromDatabase(naciatneUcty, jLabel9, databasePath, jProgressBar1, jButton4,jButton1));
}
}
This works great ,even if debug last line of code in SwingWorker done() I can clearly see that ArrayList ucty contians data from databse.
Once back to main gui , ArrayList naciatneUcty is still null.
But it should not be since I am sending it to SwingWorker where its reference should be updated...
Where is the problem ,why reference is not updated at all?
Java references are passed by value, so when you execute ucty = get() the reference inside the swingworker is changed, but that isn't going to update the reference for naciatneUcty. You'd be better off instantiating naciatneUcty before running the swingworker, have doInBackground() return void, scrap utcy2 and then just grab utcy in the done() method.
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;
}