I am successfully using H2 database in AUTO_SERVER mode so that a database file is shared among a number of desktop clients on a network transparently.
This way a server is elected among the clients and all other clients read from the tcp server.
What I'm missing is how a client or the server can notify all other desktop clients something has been changed in the database.
Right now I'm using a JGroups channel to let all clients comunicate one with each other however this is another point of failure and another leader election algorithm which runs in parallel with H2.
Isn't there any other method?
I have read about the JMS (Java Message Service Java API) which is supported in some databases. Any hint for H2?
Thanks
EDIT:
The following code is an adaptation of the current answer, if I start the Sender first (set args as "sender") he connects as server to the H2 database, then I execute Receiver (set args as "receiver") in remote machines and they connect as clients.
Yet only the server receives notifications, clients don't receive anything.
This makes sense from what I currently know: a trigger is only called on the server, a user defined function called from a client or server is called on the client or server but not across all clients (and server) connected to the database.
So is there a way to adapt the below to notify all connected instances of a change in the database?
import java.io.File;
import java.sql.*;
import java.util.concurrent.atomic.AtomicLong;
import org.h2.tools.TriggerAdapter;
public class TestSimpleDB2
{
public static void main(String[] args) throws Exception
{
//final String url = "jdbc:h2:mem:test;multi_threaded=true";
final String url = "jdbc:h2:" + File.separator + "mnt/testdir/PlanIGS" + File.separator
+ "persondb;create=true;AUTO_SERVER=TRUE;multi_threaded=true";
Connection conn = DriverManager.getConnection(url);
Statement stat = conn.createStatement();
boolean isSender = false;
args = new String[]
{
"sender"
};
for (String arg : args)
{
if (arg.contains("receiver"))
{
System.out.println("receiver starting");
isSender = false;
}
else if (arg.contains("sender"))
{
System.out.println("sender starting");
isSender = true;
}
}
if (isSender)
{
stat.execute("create alias wait_for_change for \""
+ TestSimpleDB2.class.getName()
+ ".waitForChange\"");
stat.execute("create table test(id identity)");
stat.execute("create trigger notifier "
+ "before insert, update, delete, rollback "
+ "on test call \""
+ TestSimpleDB2.Notifier.class.getName() + "\"");
Thread.sleep(1000);
for (int i = 0; i < 10; i++)
{
System.out.println("Sender: I change something...");
stat.execute("insert into test values(null)");
Thread.sleep(2000);
}
}
else
{
new Thread()
{
public void run()
{
try
{
Connection conn = DriverManager.getConnection(url);
for (int i = 0; i < 10; i++)
{
conn.createStatement().execute(
"call wait_for_change(100000)");
System.out.println("Receiver: event received");
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
}.start();
}
conn.close();
}
static AtomicLong modCount = new AtomicLong();
public static void waitForChange(long maxWaitMillis)
{
synchronized (modCount)
{
try
{
modCount.wait(maxWaitMillis);
}
catch (InterruptedException e)
{
// ignore
}
}
}
public static class Notifier extends TriggerAdapter
{
public void fire(Connection conn, ResultSet oldRow, ResultSet newRow)
throws SQLException
{
modCount.incrementAndGet();
synchronized (modCount)
{
modCount.notifyAll();
}
}
}
}
H2 does not implement JMS (in fact I don't know of a database that does). However, you could build a simple notify mechanism within H2, using a trigger and a user defined function, as follows. Please note this would require the multi-threaded mode in H2, which is not fully tested yet. Because of that, it might make sense to use a separate database for messaging than the database you use for your data.
import java.sql.*;
import java.util.concurrent.atomic.AtomicLong;
import org.h2.tools.TriggerAdapter;
public class TestSimpleDb {
public static void main(String[] args) throws Exception {
final String url = "jdbc:h2:mem:test;multi_threaded=true";
Connection conn = DriverManager.getConnection(url);
Statement stat = conn.createStatement();
stat.execute("create alias wait_for_change for \"" +
TestSimpleDb.class.getName() +
".waitForChange\"");
stat.execute("create table test(id identity)");
stat.execute("create trigger notifier " +
"before insert, update, delete, rollback " +
"on test call \"" +
TestSimpleDb.Notifier.class.getName() + "\"");
new Thread() {
public void run() {
try {
Connection conn = DriverManager.getConnection(url);
for (int i = 0; i < 10; i++) {
conn.createStatement().execute(
"call wait_for_change(10000)");
System.out.println("Receiver: event received");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
Thread.sleep(500);
for (int i = 0; i < 10; i++) {
System.out.println("Sender: I change something...");
stat.execute("insert into test values(null)");
Thread.sleep(1000);
}
conn.close();
}
static AtomicLong modCount = new AtomicLong();
public static void waitForChange(long maxWaitMillis) {
synchronized (modCount) {
try {
modCount.wait(maxWaitMillis);
} catch (InterruptedException e) {
// ignore
}
}
}
public static class Notifier extends TriggerAdapter {
public void fire(Connection conn, ResultSet oldRow, ResultSet newRow)
throws SQLException {
modCount.incrementAndGet();
synchronized (modCount) {
modCount.notifyAll();
}
}
}
}
Related
I have client like this :
import org.basex.api.client.ClientSession;
#Slf4j
#Component(value = "baseXAircrewClient")
#DependsOn(value = "baseXAircrewServer")
public class BaseXAircrewClient {
#Value("${basex.server.host}")
private String basexServerHost;
#Value("${basex.server.port}")
private int basexServerPort;
#Value("${basex.admin.password}")
private String basexAdminPassword;
#Getter
private ClientSession session;
#PostConstruct
private void createClient() throws IOException {
log.info("##### Creating BaseX client session {}", basexServerPort);
this.session = new ClientSession(basexServerHost, basexServerPort, UserText.ADMIN, basexAdminPassword);
}
}
It is a singleton injected in a service which run mulitple queries like this :
Query query = client.getSession().query(finalQuery);
return query.execute();
All threads query and share the same session.
With a single thread all is fine but with multiple thread I get some random (and weird) error, like the result of a query to as a result of another.
I feel that I should put a synchronized(){} arround query.execute() or open and close session for each query, or create a pool of session.
But I don't find any documentation how the use the session in parrallel.
Is this implementation fine for multithreading (and my issue is comming from something else) or should I do it differently ?
I ended creating a simple pool by adding removing the client from a ArrayBlockingQueue and it is working nicely :
#PostConstruct
private void createClient() throws IOException {
log.info("##### Creating BaseX client session {}", basexServerPort);
final int poolSize = 5;
this.resources = new ArrayBlockingQueue < ClientSession > (poolSize) {
{
for (int i = 0; i < poolSize; i++) {
add(initClient());
}
}
};
}
private ClientSession initClient() throws IOException {
ClientSession clientSession = new ClientSession(basexServerHost, basexServerPort, UserText.ADMIN, basexAdminPassword);
return clientSession;
}
public Query query(String finalQuery) throws IOException {
ClientSession clientSession = null;
try {
clientSession = resources.take();
Query result = clientSession.query(finalQuery);
return result;
} catch (InterruptedException e) {
log.error("Error during query execution: " + e.getMessage(), e);
} finally {
if (clientSession != null) {
try {
resources.put(clientSession);
} catch (InterruptedException e) {
log.error("Error adding to pool : " + e.getMessage(), e);
}
}
}
return null;
}
I am try to connect SQL Server using multi-threading the execute SQL query on tables.
When I try to connect SQL server it's working fine without any issue, but when try to connect using thread mechanism it displaying exception.
Here is my code can exception message please let me know how to resolve this issue.
using System;
using System.Data.SqlClient;
using NUnit.Framework;
using System.Threading;
namespace ThreadSQLConnect
{
[TestFixture]
public class SQLConnect
{
private static object threadLock = new object();
[Test]
public void temp()
{CreateThread(1); }
public static void CreateThread(int ThreadCount)
{
Thread[] workerThread = new Thread[ThreadCount];
for (int i = 0; i < ThreadCount; i++)
{
workerThread[i] = new Thread(new ThreadStart(ConnectDBAndExecuteQueryWithLock));
workerThread[i].Name = i.ToString();
workerThread[i].Start();
}
}
public static void ConnectDBAndExecuteQueryWithLock()
{
lock (threadLock)
{
try
{
string sqlQuery = "insert into tblMasterLookup Values (" + Thread.CurrentThread.Name + ",'Test','2.0','Myapplication',GetDate())";
string connectionString = string.Format("Server={0}; Database = {1}; User Id = {2}; Password = {3};",
"serverNname",
"Databasename",
"ReadOnlyUser",
"ReadOnly12");
using (SqlConnection connection = new SqlConnection(connectionString))
{
try
{
connection.Open();
}
catch(Exception e)
{ Console.WriteLine(e.StackTrace); }
using (SqlCommand command = new SqlCommand(sqlQuery, connection))
{
command.CommandTimeout = 80;
command.ExecuteNonQuery();
Console.WriteLine("Executed Thread.. " + Thread.CurrentThread.Name);
}
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
}
}
Here is exception message
I want to execute a method periodically, this method get informations from database it show it into a label, I tried the following code :
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
//update information
miseAjour();
}
}, 0, 2000);
when i run the main program, the background service run also normaly but when the informations changes on db i get this exception:
Exception in thread "Timer-0" java.lang.IllegalStateException: Not on FX application thread; currentThread = Timer-0
And this is the code of method miseAjour :
public void miseAjour(){
try {
dbConnection db = new dbConnection();
Connection connect = db.connectiondb();
connect.setAutoCommit(false);
Statement stmt= connect.createStatement();
ResultSet rs = stmt.executeQuery("SELECT count(*) as nbrAderent FROM gss_aderent ");
int nbrAderent = rs.getInt("nbrAderent");
rs.close();
stmt.close();
connect.commit();
connect.close();
main_nbrAdrTot.setText(nbrAderent + "");
} catch (SQLException ex) {
Logger.getLogger(SimpleController.class.getName()).log(Level.SEVERE, null, ex);
}
}
You can Timer for this, but I would recommend to use the JavaFX provided API called as ScheduledService.
ScheduledService is made to execute the same Task at regular intervals and since it creates a Task internally, there are API which help you to bind the value to the UI controls.
ScheduledService<Object> service = new ScheduledService<Object>() {
protected Task<Object> createTask() {
return new Task<Object>() {
protected Object call() {
// Call the method and update the message
updateMessage(miseAjour());
return object; // Useful in case you want to return data, else null
}
};
}
};
service.setPeriod(Duration.seconds(10)); //Runs every 10 seconds
//bind the service message properties to your Label
label.textProperty().bind(service.messageProperty()); // or use your label -> main_nbrAdrTot
Inside the dbcall method miseAjour, return the value that you have fetched and you want to update the label with :
public String miseAjour(){
String nbrAderent = null;
try {
dbConnection db = new dbConnection();
Connection connect = db.connectiondb();
connect.setAutoCommit(false);
Statement stmt= connect.createStatement();
ResultSet rs = stmt.executeQuery("SELECT count(*) as nbrAderent FROM gss_aderent ");
nbrAderent = String.valueOf(rs.getInt("nbrAderent"));
connect.commit();
} catch (SQLException ex) {
Logger.getLogger(SimpleController.class.getName()).log(Level.SEVERE, null, ex);
}
finally {
rs.close();
stmt.close();
connect.close();
}
return nbrAderent;
}
Finnaly i resolved the problem ,here is the code :
public class TimerServiceApp {
public void start() throws Exception {
TimerService service = new TimerService();
service.setPeriod(Duration.seconds(10));
service.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent t) {
main_nbrAdrTot.setText(t.getSource().getMessage());
}
});
service.start();
}
private class TimerService extends ScheduledService<Integer> {
private final StringProperty nbrTotAderent = new SimpleStringProperty();
public final void setTotalAderentNumber(String value ) {
nbrTotAderent.set(value);
}
public String getTotalAderentNumber() throws SQLException {
String nbrAderent = null;
ResultSet rs=null;
Statement stmt=null;
Connection connect=null;
try {
dbConnection db = new dbConnection();
connect = db.connectiondb();
connect.setAutoCommit(false);
stmt= connect.createStatement();
rs = stmt.executeQuery("SELECT count(*) as nbrAderent FROM gss_aderent ");
nbrAderent = String.valueOf(rs.getInt("nbrAderent"));
connect.commit();
} catch (SQLException ex) {
Logger.getLogger(SimpleController.class.getName()).log(Level.SEVERE, null, ex);
}
finally {
rs.close();
stmt.close();
connect.close();
}
System.out.println(" Total aderent number updated to :" + nbrAderent + " Aderents ");
return nbrAderent;
}
protected Task<Integer> createTask() {
return new Task<Integer>() {
protected Integer call() throws SQLException {
nbrTotAderent.setValue(getTotalAderentNumber());
updateMessage(getTotalAderentNumber());
return Integer.parseInt(getTotalAderentNumber());
}
};
}
}
} `
and i called this service by :
TimerServiceApp s = new TimerServiceApp();
s.start();
i dont know if the solution is optimised but it work :) thank you #ItachiUchiha i took the solution from yout answer in the following link
I am Using GCM (Google Cloud Messaging).In that what i want i want to send J Son from the server side .On Client side I want to receive that for simple message i have done but i am stucked how could i pass J Son from the server side to the client side.
Please help me to resolve this.
This is my Server side code
public class GCMBroadcast extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final String SENDER_ID = "";
private static final String ANDROID_DEVICE = "";
private List<String> androidTargets = new ArrayList<String>();
public GCMBroadcast() {
super();
androidTargets.add(ANDROID_DEVICE);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String collapseKey = "";
String userMessage = "";
try {
userMessage = request.getParameter("Message");
collapseKey = request.getParameter("CollapseKey");
} catch (Exception e) {
e.printStackTrace();
return;
}
Sender sender = new Sender(SENDER_ID);
Message message = new Message.Builder()
.collapseKey(collapseKey)
.addData("message", userMessage)
.build();
try {
MulticastResult result = sender.send(message, androidTargets, 1);
System.out.println("Response: " + result.getResults().toString());
if (result.getResults() != null) {
int canonicalRegId = result.getCanonicalIds();
if (canonicalRegId != 0) {
System.out.println("response " +canonicalRegId );
}
} else {
int error = result.getFailure();
System.out.println("Broadcast failure: " + error);
}
} catch (Exception e) {
e.printStackTrace();
}
request.setAttribute("CollapseKey", collapseKey);
request.setAttribute("Message", userMessage);
request.getRequestDispatcher("XX.jsp").forward(request, response);
}
}
Your payload (added to the Message by calls to addData) can only be name/value pairs. If you want to send a JSON, you can put a JSON string in the value of such name/value pair. Then you'll have to parse that JSON yourself in the client side.
For example :
.addData("message","{\"some_json_key\":\"some_json_value\"}")
I've been working with GWT and appengine. Now I want to change my database to a relational one. I prefer PostgreSQL over MySQL because of the schema architecture.
I already work in projects with JDBC, but I cannot make it work in my appengine project.
What am I doing wrong?
I think you are mistake. If you want your backend to connect to JDBC you have to disable appengine.
I recommend creating a new web project with the google eclipse plugin but without appengine. Then copy all the source files and start from there.
I let you my class to connect to PostgreSQL vua JDBC
public class ConexionBD
{
private Vector <Connection> connections = new Vector<Connection>();
protected int cantCon=3;
private static ConexionBD pool;
private ConexionBD(){
for (int i= 0; i< cantCon;i++)
connections.add(connect());
}
public static ConexionBD getPoolConnection(){
if (pool== null)
pool =new ConexionBD();
return pool;
}
private Connection connect()
{
try
{
//Setear driver
Class.forName ("org.postgresql.Driver").newInstance ();
Connection con = DriverManager.getConnection ("jdbc:postgresql://localhost:5432/llevomisnegocios","postgres","root");
return con;
}
catch (SQLException e){
System.out.println("Mensaje Error al Conectar: " + e.getMessage());
//System.out.println("Stack Trace: " + e.getStackTrace());
return null;
}catch (Exception ex){
System.out.println("Mensaje Error al Conectar: " + ex.getMessage());
//System.out.println("Stack Trace: " + ex.getStackTrace());
return null;
}
}
public void closeConnections(){
for (int i=0; i<connections.size();i++)
{
try
{
connections.elementAt(i).close();
}
catch(Exception e)
{
System.out.println("Mensaje Error: " + e.getMessage());
System.out.println("Stack Trace: " + e.getStackTrace());
}
}
}
public Connection getConnection(){
Connection c = null;
if (connections.size()>0)
c = connections.remove(0);
else{
c = connect();
System.out.println("PoolConnection: Conexion Exitosa con la BD");
}
return c;
}
public void realeaseConnection(Connection c){
connections.add(c);
}
}