0

I'm trying to use H2 Trigger facility to let clients connected to a H2 database in automatic mixed mode (AUTO_SERVER=TRUE) receive notification when something changes in the database table

test(id INTEGER NOT NULL AUTO_INCREMENT, message varchar(1024))

So far only the H2 server receives the TRIGGER notification, while clients cannot receive any notification therefore their only way to check for changes to the database is to poll with queries to the table, but this way the TRIGGER itself is useless, I could just simply all clients and server poll the database for changes!.

Is there some way to let a trigger notify all clients connected or call a method inside each client so that they realize the table has been modified with an insertion (doesn't bother me the delete or update cases)?

I post my code below which is based on this answer by Thomas Mueller (H2 database creator):

import java.sql.*;
import java.util.concurrent.atomic.AtomicLong;
import org.h2.api.Trigger;

public class TestSimpleDb
{
    public static void main(String[] args) throws Exception
    {
        final String url = "jdbc:h2:test;create=true;AUTO_SERVER=TRUE;multi_threaded=true";

        boolean isSender = false;
        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)
        {
            Connection conn = DriverManager.getConnection(url);
            Statement stat = conn.createStatement();
            stat.execute("create table test(id INTEGER NOT NULL AUTO_INCREMENT, message varchar(1024))");
            stat.execute("create trigger notifier "
                    + "before insert, update, delete, rollback "
                    + "on test FOR EACH ROW call \""
                    + TestSimpleDb.Notifier.class.getName() + "\"");

            Thread.sleep(500);

            for (int i = 0; i < 10; i++) {
                System.out.println("Sender: I change something...");
                stat.execute("insert into test(message) values('my message')");
                Thread.sleep(1000);
            }
            conn.close();
        }
        else 
        {
            new Thread() {
                public void run() {
                    try {
                        Connection conn = DriverManager.getConnection(url);
                        while (true) {
                            ;
                            //this loop is just to keep the thread alive..
                        }
                    }
                    catch (Exception e)
                    {
                        e.printStackTrace();
                    }
                }
            }.start();
        }
    }

    public static class Notifier implements Trigger
    {
        @Override
        public void init(Connection cnctn, String string, String string1, String string2, boolean bln, int i) throws SQLException {
            // Initializing trigger
        }

        @Override
        public void fire(Connection conn, Object[] oldRow, Object[] newRow) throws SQLException {
            if (newRow != null) {
                System.out.println("Received: " + (String) newRow[1]);
            }
        }

        @Override
        public void close() {
            // ignore
        }

        @Override
        public void remove() {
            // ignore
        }
    }
}
Community
  • 1
  • 1
dendini
  • 3,842
  • 9
  • 37
  • 74

1 Answers1

0

Like all triggers, this trigger is called on the server, that is, when using the automatic mixed mode (like you do) in the process that opened the database first. Therefore, if I first start the "sender", then I get the following output there:

sender starting
Sender: I change something...
Received: my message
Sender: I change something...
Received: my message

and if I then start the "receiver", I get the following messages there:

Receiver: event received
Receiver: event received
Receiver: event received

If you want that the "receiver" can display what rows were changed, you would need a different architecture. For example, you could add a timestamp column to the table (and an index for this column), and then, on the receiver side, query for the rows where the timestamp is new. This will only work for added and changed rows; for removed rows, you might need to add a new table that contains the removed rows since time x. This table would need to be garbage collected from time to time so that it doesn't grow forever.

Thomas Mueller
  • 48,905
  • 14
  • 116
  • 132
  • You could keep all entries in a linked list, or (maybe better) a TreeMap. That way you don't need any table. The sender and the receiver could both use a user defined function; one called `send` and another call `receive`. That would reduce the overhead. – Thomas Mueller Oct 11 '13 at 17:40
  • I don't agree with your answer above, if I first start the sender as server of course he gets the received message notification because he receives the trigger, if however I then start a receiver (he is a client so he won't receive any trigger) this one will not receive any event! Can you tell me how all clients can receive an event from a database insert? Thank you – dendini Oct 31 '13 at 17:05
  • This is a new requirement (you didn't write about that before). Well, you would need to use a queue (possibly a persistent queue). – Thomas Mueller Nov 01 '13 at 10:32
  • I updated the question to make it more straight, the problem is the same only explained better :). The table with a timestamp and the message is perfectly ok, so the queue is not needed I think, I miss how clients can be notified of something new in the table. The trigger is not enough as it only notifies the H2 server, the user defined function I can't understand how it can be called on each client when something changes in the table. – dendini Nov 01 '13 at 11:57
  • Well, look at [my answer for your other question](http://stackoverflow.com/questions/19291215/using-h2-database-server-how-to-notify-changes-to-clients-jms-messaging). The user defined function needs to be called _before_ something changed. The user defined function then returns as soon as there was some change. – Thomas Mueller Nov 01 '13 at 12:35
  • I edited my other question, the code there doesn't work across clients in different computers with AUTO_SERVER=TRUE. I cannot understand why an UDF should return on all connected clients when a modification from a remote client or the server is made. Notice this proof of concept code should allow me to do some sort of notification/messaging across multiple connected machines to the same database without polling the database for changes. – dendini Nov 04 '13 at 09:08
  • Sorry, I don't know why it doesn't work for you, but it works for me (my test case, that is). Of course the function `call wait_for_change(10000)` needs to be called from all clients that want to get notified. – Thomas Mueller Nov 04 '13 at 10:59