java.lang.Object
dk.cloudcreate.essentials.components.foundation.postgresql.ListenNotify

public final class ListenNotify extends Object
Helper class for setting up the NOTIFY part of the classical Postgresql LISTEN/NOTIFY concept.
See https://jdbc.postgresql.org/documentation/81/listennotify.html and
https://www.postgresql.org/docs/current/sql-notify.html
See Also:
  • Field Details

  • Constructor Details

    • ListenNotify

      public ListenNotify()
  • Method Details

    • resolveTableChangeChannelName

      public static String resolveTableChangeChannelName(String tableName)
      Resolve the Postgresql LISTEN/NOTIFY channel name used to communicate each notification related to the changes that we NOTIFY, based on the addChangeNotificationTriggerToTable(Handle, String, List, String...) setup.
      Parameters:
      tableName - the name of the table
      Returns:
      the channel name
    • addChangeNotificationTriggerToTable

      public static void addChangeNotificationTriggerToTable(org.jdbi.v3.core.Handle handle, String tableName, List<ListenNotify.SqlOperation> triggerOnSqlOperations, String... includeAdditionalTableColumnsInNotificationPayload)
      Create (or replace an existing) Table change notification FUNCTION and an AFTER TRIGGER to the given Table in order to support the classical Postgresql LISTEN/NOTIFY concept.
      The NOTIFY/LISTEN channel is defined by the resolveTableChangeChannelName(String) and the payload is a JSON formatted string containing a "table_name" property as well as any additional columns provided in the includeAdditionalTableColumnsInNotificationPayload parameter.

      You can use listen(Jdbi, String, Duration) to setup asynchronous change notification

      Important information from https://www.postgresql.org/docs/current/sql-notify.html:

      NOTIFY interacts with SQL transactions in some important ways. Firstly, if a NOTIFY is executed inside a transaction, the notify events are not delivered until and unless the transaction is committed. This is appropriate, since if the transaction is aborted, all the commands within it have had no effect, including NOTIFY. But it can be disconcerting if one is expecting the notification events to be delivered immediately. Secondly, if a listening session receives a notification signal while it is within a transaction, the notification event will not be delivered to its connected client until just after the transaction is completed (either committed or aborted). Again, the reasoning is that if a notification were delivered within a transaction that was later aborted, one would want the notification to be undone somehow — but the server cannot “take back” a notification once it has sent it to the client. So notification events are only delivered between transactions. The upshot of this is that applications using NOTIFY for real-time signaling should try to keep their transactions short.

      If the same channel name is signaled multiple times with identical payload strings within the same transaction, only one instance of the notification event is delivered to listeners. On the other hand, notifications with distinct payload strings will always be delivered as distinct notifications.
      Example:
      Given this table definition:
      CREATE TABLE test_table (id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, column1 TEXT, column2 TEXT)
      When adding notification trigger:
      
       jdbi.useTransaction(handle -> {
                   ListenNotify.addChangeNotificationTriggerToTable(handle, "test_table", List.of(ListenNotify.TriggerOnEvent.INSERT), "column1", "column2");
               });
       
      Then the listener subscription:
      
       subscription = ListenNotify.listen(jdbi,
                                          "test_table",
                                          Duration.ofMillis(200))
                                  .subscribe(notificationPayload -> {
                                      // Handle the String based notification payload received
                                  });
       
      will for an inserted row:
      
       jdbi.useTransaction(handle -> {
          handle.execute("INSERT INTO test_table (column1, column2) VALUES ('Column1Value', 'Column2Value')");
       });
       
      receive this payload:
      {"table_name":"test_table","column1":"Column1Value","column2":"Column2Value"}
      which you can parse into an Object using e.g. Jacksons ObjectMapper.
      If you extend TableChangeNotification you automatically inherit the mapping for the table-name and the ListenNotify.SqlOperation properties:
      
       class TestTableNotification extends TableChangeNotification {
           @JsonProperty("column1")
           private String column1;
           @JsonProperty("column2")
           private String column2;
       }
      
      
       var notification = objectMapper.readValue(notificationPayload, TestTableNotification.class);
       
      Parameters:
      handle - the jdbi handle
      tableName - the name of the table for which we want to setup a Table change notification FUNCTION and TRIGGER that can be used for LISTEN/NOTIFY
      triggerOnSqlOperations - what type of should result in a notification
      includeAdditionalTableColumnsInNotificationPayload - any additional column names that should be included in the NOTIFY JSON formatted String payload
    • removeChangeNotificationTriggerFromTable

      public static void removeChangeNotificationTriggerFromTable(org.jdbi.v3.core.Handle handle, String tableName)
      Remove Table change notification FUNCTION and an AFTER TRIGGER to the given Table in order to support the classical Postgresql LISTEN/NOTIFY concept.
      Parameters:
      handle - the jdbi handle
      tableName - the name of the table for which we want to remove the Table change notification FUNCTION and TRIGGER used for LISTEN/NOTIFY
    • listen

      public static reactor.core.publisher.Flux<String> listen(org.jdbi.v3.core.Jdbi jdbi, String tableName, Duration pollingInterval)
      Listen to notifications related to the given table that was setup using addChangeNotificationTriggerToTable(Handle, String, List, String...)
      As the default Postgresql driver doesn't support asynchronous notification, we're using a polling mechanism.
      Note: If you need to listen to notification from multiple tables, then you MUST use MultiTableChangeListener instead.
      Example:
      
       subscription = ListenNotify.listen(jdbi,
                                          "test_table",
                                          Duration.ofMillis(200))
                                  .subscribe(notificationPayload -> {
                                      // Handle the String based notification payload received
                                  });
       
      Parameters:
      jdbi - the jdbi instance
      tableName - the name of the table for which we want to setup a Table change notification FUNCTION and TRIGGER that can be used for LISTEN/NOTIFY
      pollingInterval - the interval between each polling attempt
      Returns:
      a Flux that contains the notification payload as a String.
      See Also: