53

From what I can tell, the JDBC drivers for LISTEN/NOTIFY in Java do NOT support true event-driven notifications. You have to poll the database every so often to see if there's a new notification.

What options do I have in Java (possibly something other than JDBC?), if any, to get notifications asynchronously in a true event-driven manner without polling?

6
  • If you want notifications, you need an entity to emit events. If PostgreSQL does not emit events (on a JMS topic or the like) you cannot have event-driven notifications. Commented Feb 7, 2014 at 17:15
  • 3
    PostgreSQL provides for LISTEN/NOTIFY, which I understand to be an asynchronous notification mechanism. The problem is that Java JDBC doesn't provide support for the asynchronous notifications and requires polling. Commented Feb 7, 2014 at 17:18
  • Sorry. I see what you mean... Looked at the driver documentation again. Commented Feb 7, 2014 at 17:23
  • The way it can be done in other languages is that you ask the database handle for the underlying file descriptor of the connection socket descriptor (promising never to read or write that descriptor yourself) then you sleep on that descriptor to be become readable. Once it is readable, you can query for notifications on the database handle as usual. I don't know how you translate this into Java-ese, though. Commented Feb 7, 2014 at 18:29
  • 1
    @jasons2645 Sorry, Java sends me into fits of rage whenever I try to do anything non-trivial with it. If the connection is used for nothing other than getting notifications once the LISTEN has been done, then it would probably best to put the connection into a dedicated thread, and have it block until something happens. My best guess would be to expose org/postgresql/core/v3/ProtocolConnectionImpl.java's Peek() up the chain until it is visible to org.postgresql.PGConnection Commented Feb 10, 2014 at 19:04

5 Answers 5

44

Use the pgjdbc-ng driver.

http://impossibl.github.io/pgjdbc-ng/

It supports async notifications, without polling. I have used it successfully.

See https://database-patterns.blogspot.com/2014/04/postgresql-nofify-websocket-spring-mvc.html

Source code: https://bitbucket.org/neilmcg/postgresql-websocket-example

Oleg has a nice example answer as well

Sign up to request clarification or add additional context in comments.

6 Comments

Also see @Oleg Mikhailov's example code below, which uses pgjdbc-ng.
great stuff, I would like to build web stream where I will stream some new records inserted in database, I see I could do it without first pushing them into some message broker, but could use directly db queue, brilliant.
Instead of pointing to a blog post, it would be more useful to include the sample code here.
@Neil McGuigan blog url is not functioning please check it out.
If I'm using a connection pool, is there a way to make sure the connection is not closed as the listening is indefinite?
|
41

Here's an asynchronous pattern using com.impossibl.postgres.api (pgjdbc-ng-0.6-complete.jar) with JDK 1.8:

import com.impossibl.postgres.api.jdbc.PGConnection;
import com.impossibl.postgres.api.jdbc.PGNotificationListener;
import com.impossibl.postgres.jdbc.PGDataSource;    
import java.sql.Statement;

public static void listenToNotifyMessage() {
    PGDataSource dataSource = new PGDataSource();
    dataSource.setHost("localhost");
    dataSource.setPort(5432);
    dataSource.setDatabase("database_name");
    dataSource.setUser("postgres");
    dataSource.setPassword("password");

    PGNotificationListener listener = (int processId, String channelName, String payload) 
        -> System.out.println("notification = " + payload);

    try (PGConnection connection = (PGConnection) dataSource.getConnection()) {
        Statement statement = connection.createStatement();
        statement.execute("LISTEN test");
        statement.close();
        connection.addNotificationListener(listener);
        while (true){ }
    } catch (Exception e) {
        System.err.println(e);
    }
}

Create a trigger function for your database:

CREATE OR REPLACE FUNCTION notify_change() RETURNS TRIGGER AS $$
    BEGIN
        SELECT pg_notify('test', TG_TABLE_NAME);
        RETURN NEW;
    END;
$$ LANGUAGE plpgsql;

Assign a trigger for every table you want to track:

CREATE TRIGGER table_change 
    AFTER INSERT OR UPDATE OR DELETE ON table_name
    FOR EACH ROW EXECUTE PROCEDURE notify_change();

4 Comments

I am also doing as there in your post. May I know what's the significance of while(true){} ?? I also posted a similar question regarding my earlier issue: stackoverflow.com/questions/37916489/…
The while(true) {} isn't needed, except that the try loop will terminate -- and close the Connection -- if there isn't something to stop it. The connection needs to remain open for the NOTIFY messages to work.
What's the point of using SELECT pg_notify() in a PL/PgSQL procedure, when a simple pure SQL procedure of just NOTIFY test would do just fine?
@SergeiTachenov you can use NOTIFY but its value must be a static string whereas pg_notify() can use a static string or variable
2

The official PostgreSQL JDBC driver now has support for LISTEN and NOTIFY, see https://jdbc.postgresql.org/documentation/server-prepare/#listen--notify

1 Comment

from the page you have shared "A key limitation of the JDBC driver is that it cannot receive asynchronous notifications and must poll the backend to check if any notifications were issued. A timeout can be given to the poll function, but then the execution of statements from other threads will block."
-1

I found the most common PostgreSQL driver in Java from org.postgresql, unfortunately, cannot handle callbacks from database. The other option mentioned pgjdbc-ng seems was popular, however it is not maintained in timely manner and have few vulnerable dependencies.

The option I found working best is a pg-client database driver from Vert.x. The essential idea is the client is operate in reactive way. Let’s have a closer look at implementation:

public static void listen(PgConnection connection) {

        connection.notificationHandler(notification ->
            System.out.println("Received " + notification.getPayload() + " on channel " + notification.getChannel()));

        connection
                .query("LISTEN hero_update")
                .execute()
                .onComplete(ar -> System.out.println("Subscribed to channel"));
    }

Comments

-3

It appears as though there's no way around this. You can work around it, as several suggestions have been made along those lines, but ultimately, you're going to be polling.

1 Comment

This answer is no longer valid.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.