Skip to Main Content
  • Questions
  • Continuous Query Notification (CQN) feature does not work with ADB

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, Rajat.

Asked: February 03, 2023 - 7:04 am UTC

Last updated: April 04, 2023 - 5:48 am UTC

Version: 19c

Viewed 1000+ times

You Asked

We are trying to perform a simple POC to make use of the CQN feature with ADW and ATP. We are following the walkthrough example available at :
https://docs.oracle.com/en/
database/oracle/oracle-database/19/jjdbc/continuos-query-notification.html#GUID-17D0D7C5-77C9-420D-9D13-F668C1056792


When we run the following JAVA code, Database change registrations are getting created properly. We can see them getting created in USER_CHANGE_NOTIFICATION_REGS table . But we are not receiving any object change notifications.
Similar database setup and JAVA code works perfectly with oracle 19c Database installed locally. And we are receiving the notifications.

The ADW instance that we are using has been configured with Access type as Allow secure access from everywhere using mTLS authentication. Please let us know if you need more info about the ADW instance.


Database setup:
We used the administrator user to connect to the ADW instance and created another user, granted the CHANGE NOTIFICATION priviliges and created a test table for trying out the object change notification on it using this SQL
/* creating user and granting priviliges */
CREATE USER MAXTEST IDENTIFIED BY ********
  DEFAULT TABLESPACE users
  TEMPORARY TABLESPACE temp
  QUOTA UNLIMITED ON users;
  
GRANT CONNECT, CREATE TABLE, CREATE PROCEDURE, CREATE SEQUENCE TO MAXTEST;
GRANT CHANGE NOTIFICATION TO MAXTEST;
GRANT EXECUTE ON DBMS_CHANGE_NOTIFICATION TO MAXTEST;

/* creating table to be used for object change notification */
CREATE TABLE t1 (
  id           NUMBER,
  description  VARCHAR2(50),
  CONSTRAINT t1_pk PRIMARY KEY (id)
);

INSERT INTO t1 (id, description) VALUES (1, 'One');
INSERT INTO t1 (id, description) VALUES (2, 'Two');
COMMIT;



JAVA code which works with locally installed oracle DB but now with ADW:
package com.oracle;

import oracle.jdbc.OracleConnection;
import oracle.jdbc.OracleDriver;
import oracle.jdbc.OracleStatement;
import oracle.jdbc.dcn.DatabaseChangeEvent;
import oracle.jdbc.dcn.DatabaseChangeListener;
import oracle.jdbc.dcn.DatabaseChangeRegistration;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class DBChangeNotification
{
    static final String USERNAME= "MAXTEST";
    static final String PASSWORD= "********";
    static String URL = "jdbc:oracle:thin:@phxdd1maxdldbpoc_high?TNS_ADMIN=/path/to/downloaded/wallet";
//    static String URL = "jdbc:oracle:thin:@localhost:1521/ORCLCDB";


    public static void main(String[] argv)
    {
        DBChangeNotification demo = new DBChangeNotification();
        try
        {
            demo.run();
        }
        catch(SQLException mainSQLException )
        {
            mainSQLException.printStackTrace();
        }
    }

    void run() throws SQLException
    {
        OracleConnection conn = connect();
        Properties prop = new Properties();


        prop.setProperty(OracleConnection.DCN_CLIENT_INIT_CONNECTION, "true");
        prop.setProperty(OracleConnection.DCN_NOTIFY_ROWIDS,"true");

        DatabaseChangeRegistration dcr = conn.registerDatabaseChangeNotification(prop);
        
        try
        {
            // add the listener:
            DCNDemoListener list = new DCNDemoListener();
            dcr.addListener(list);

            // second step: add objects in the registration:
            Statement stmt = conn.createStatement();
            // associate the statement with the registration:
            ((OracleStatement)stmt).setDatabaseChangeRegistration(dcr);
            ResultSet rs = stmt.executeQuery("select * from T1");
            while (rs.next())
            {}
            System.out.println("Username is " + dcr.getUserName());
            String[] tableNames = dcr.getTables();
            for(int i=0;i<tableNames.length;i++)
                System.out.println(tableNames[i]+" is part of the registration.");
            rs.close();
            stmt.close();
        }
        catch(SQLException ex)
        {
            // if an exception occurs, we need to close the registration in order
            // to interrupt the thread otherwise it will be hanging around.
            if(conn != null)
                conn.unregisterDatabaseChangeNotification(dcr);
            throw ex;
        }
        finally
        {
            try
            {
                // Note that we close the connection!
                conn.close();
            }
            catch(Exception innerex){ innerex.printStackTrace(); }
        }

        // create an infinite loop to keep the application running and wait for notification
        try {
            while (true) {
                System.out.println("Waiting Notifications");
                Thread.sleep(5000);
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            OracleConnection conn3 = connect();
            conn3.unregisterDatabaseChangeNotification(dcr);
            conn3.close();
        }
    }

    /**
     * Creates a connection the database.
     */
    OracleConnection connect() throws SQLException
    {
        OracleDriver dr = new OracleDriver();
        Properties prop = new Properties();
        prop.setProperty("user", DBChangeNotification2.USERNAME);
        prop.setProperty("password", DBChangeNotification2.PASSWORD);
        return (OracleConnection)dr.connect(DBChangeNotification2.URL,prop);
    }
}

/**
 * DCN listener: it prints out the event details in stdout.
 */
class DCNDemoListener implements DatabaseChangeListener
{
    public void onDatabaseChangeNotification(DatabaseChangeEvent e)
    {
        Thread t = Thread.currentThread();
        System.out.println("DCNDemoListener: got an event ("+this+" running on thread "+t+")");
        System.out.println(e.getEventType().toString());
        System.out.println(e.toString());
    }
}



When the application is running, I execute the following SQL to insert an entry in the table which is part being tracked by the registration.
INSERT INTO t1 VALUES (3, 'Three');
COMMIT;



Output when we run by connecting with the ADW instance - No notification received:
Username is MAXTEST
MAXTEST.T1 is part of the registration.
Waiting Notifications
Waiting Notifications
Waiting Notifications
Waiting Notifications
Waiting Notifications
Waiting Notifications
...



Output when connected with the local oracle 19c DB - Notification is received:
Username is MAXTEST
MAXTEST.T1 is part of the registration.
Waiting Notifications
Waiting Notifications
Waiting Notifications
Waiting Notifications
Waiting Notifications
DCNDemoListener: got an event (com.oracle.DCNDemoListener@4afd2e2f running on thread Thread[Thread-1,5,main])
OBJCHANGE
Registration ID         : 1269
Notification version    : 1
Event type              : OBJCHANGE
Database name           : ORCLCDB
Table Change Description (length=1)
    operation=[INSERT], tableName=MAXTEST.T1, objectNumber=73636
    Row Change Description (length=1):
      ROW:  operation=INSERT, ROWID=AAAR+kAAHAAAAFfAAA

Waiting Notifications
Waiting Notifications
...



Does this feature work with ADW/ATP ?
Are there any other configuration properties that should be setup when working with ADW ?
Is there something that we are missing here?

and Connor said...

I had a chat to the PMs on this.

Currently *client* initiated CQN is supported on Autonomous

As per the docs:

To create a CQN registration, you can use either the PL/SQL interface or Oracle Call Interface (OCI). If you use the PL/SQL interface, the notification handler is a server-side PL/SQL stored procedure; if you use OCI, the notification handler is a client-side C callback procedure.

this means via OCI callback, so I suspect you might need to use a thick OCI-based driver for this.

Rating

  (2 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Using java based (jdbc) CQN Oracle Cloud Interface

Jakob Hammer-Jakobsen, March 24, 2023 - 3:42 pm UTC

It the conclusion that all databases hosted at Oracle Cloud Infrastructure can not use Continuous Query Notification based on Java? (its only implemented in jdbc:oracle:thin and not in jdbc:oracle:oci)?

br Jakob
Connor McDonald
April 04, 2023 - 5:48 am UTC

You will need the thick driver at the client end.

working if compiled with jdk 11 & ojdbc10.jar from db

Jakob Hammer-Jakobsen, April 11, 2023 - 3:27 pm UTC

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import oracle.jdbc.OracleConnection;
import oracle.jdbc.OracleDriver;
import oracle.jdbc.OracleStatement;
import oracle.jdbc.dcn.DatabaseChangeEvent;
import oracle.jdbc.dcn.DatabaseChangeListener;
import oracle.jdbc.dcn.DatabaseChangeRegistration;

public class QCN19demo
{
static final String USERNAME= "o";
static final String PASSWORD= "password"
static String URL;

public static void main(String[] argv)
{
if(argv.length < 1)
{
System.out.println("Error: You need to provide the URL in the first argument.");
System.out.println(" For example: > java -classpath .:ojdbc6.jar DBChangeNotification \"jdbc:oracle:thin: @(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=yourhost.yourdomain.com)(PORT=5221))(CONNECT_DATA= (SERVICE_NAME=orcl)))\"");

System.exit(1);A
}
URL = argv[0];
QCN19demo demo = new QCN19demo();
try
{
demo.run();
}
catch(SQLException mainSQLException )
{
mainSQLException.printStackTrace();
}
}

void run() throws SQLException
{
OracleConnection conn = connect();

// first step: create a registration on the server:
Properties prop = new Properties();
prop.setProperty(OracleConnection.DCN_NOTIFY_ROWIDS,"true");
prop.setProperty(OracleConnection.DCN_BEST_EFFORT,"true");
prop.setProperty(OracleConnection.DCN_CLIENT_INIT_CONNECTION, "true");
prop.setProperty(OracleConnection.DCN_QUERY_CHANGE_NOTIFICATION,"true");
DatabaseChangeRegistration dcr = conn.registerDatabaseChangeNotification(prop);

try
{
// add the listenerr:
DCNDemoListener list = new DCNDemoListener(this);
dcr.addListener(list);
Statement stmt = conn.createStatement();
((OracleStatement)stmt).setDatabaseChangeRegistration(dcr);
ResultSet rs = stmt.executeQuery("select Action, Subject, Description, category, attendees, location, StartDate, EndDate, Allday, ResponseReq, Reminder, resources, BusyStatus, serveddate, rowid from Calendarrequests where serveddate is null order by 1");
while (rs.next())
{}
String[] tableNames = dcr.getTables();
for(int i=0;i<tableNames.length;i++)
System.out.println(tableNames[i]+" is part of the registration.");
rs.close();
stmt.close();
}
catch(SQLException ex)
{
// if an exception occurs, we need to close the registration in order
// to interrupt the thread otherwise it will be hanging around.
if(conn != null)
conn.unregisterDatabaseChangeNotification(dcr);
throw ex;
}
finally
{
try
{
// Note that we close the connection!
conn.close();
}
catch(Exception innerex){ innerex.printStackTrace(); }
}

synchronized( this )
{
// The following code modifies the dept table and commits:
try
{
while(true)
{
// wait until we get the event
try{ this.wait();} catch( InterruptedException ie ) {}
}
}
catch(RuntimeException ex)
{
ex.printStackTrace();
OracleConnection conn3 = connect();
conn3.unregisterDatabaseChangeNotification(dcr);
conn3.close();
}
catch(Exception ex) { ex.printStackTrace(); }
finally
{
OracleConnection conn3 = connect();
conn3.unregisterDatabaseChangeNotification(dcr);
conn3.close();
}
}
}

/**
* Creates a connection the database.
*/
OracleConnection connect() throws SQLException
{
OracleDriver dr = new OracleDriver();
Properties prop = new Properties();
prop.setProperty("user",QCN19demo.USERNAME);
prop.setProperty("password",QCN19demo.PASSWORD);
return (OracleConnection)dr.connect(QCN19demo.URL,prop);
}
}
/**
* DCN listener: it prints out the event details in stdout.
*/
class DCNDemoListener implements DatabaseChangeListener
{
QCN19demo demo;
DCNDemoListener(QCN19demo dem)
{
demo = dem;
}
public void onDatabaseChangeNotification(DatabaseChangeEvent e)
{
Thread t = Thread.currentThread();
System.out.println("DCNDemoListener: got an event ("+this+" running on thread "+t+")");
System.out.println(e.toString());
synchronized( demo ){ demo.notify();}
}
}

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library