jdbc: notificación de cambio de base de datos de Oracle y eventos duplicados

Necesito un Listener para cualquier cambio (actualización, inserción, eliminación) de la tabla de la base de datos Oracle.

Problema: obtengo mucha detección por actualización individual en mi tabla.

Creo que es el caché de Oracle, etc.

¿Es posible detectar solo cambios reales?

mi código:

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 OracleDCN { static final String USERNAME = "scott"; static final String PASSWORD = "tiger"; static String URL = "jdbc:oracle:thin:@localhost:1521:stingdev"; public static void main(String[] args) { OracleDCN oracleDCN = new OracleDCN(); try { oracleDCN.run(); } catch (Exception ex) { ex.printStackTrace(); } } private void run() throws Exception{ OracleConnection conn = connect(); Properties prop = new Properties(); prop.setProperty(OracleConnection.DCN_NOTIFY_ROWIDS, "true"); DatabaseChangeRegistration dcr = conn.registerDatabaseChangeNotification(prop); try{ dcr.addListener(new DatabaseChangeListener() { public void onDatabaseChangeNotification(DatabaseChangeEvent dce) { System.out.println("Changed row id : "+dce.getTableChangeDescription()[0].getRowChangeDescription()[0].getRowid().stringValue()); } }); Statement stmt = conn.createStatement(); ((OracleStatement) stmt).setDatabaseChangeRegistration(dcr); ResultSet rs = stmt.executeQuery("select * from EXAMPLE where ID=1"); while (rs.next()) { } rs.close(); stmt.close(); }catch(SQLException ex){ if (conn != null) { conn.unregisterDatabaseChangeNotification(dcr); conn.close(); } throw ex; } } OracleConnection connect() throws SQLException { OracleDriver dr = new OracleDriver(); Properties prop = new Properties(); prop.setProperty("user", OracleDCN.USERNAME); prop.setProperty("password", OracleDCN.PASSWORD); return (OracleConnection) dr.connect(OracleDCN.URL, prop); } } 

Salida:

 Changed row id : AAAFSzAAAAAAAG8AAA Changed row id : AAAFSzAAAAAAAG8AAA Changed row id : AAAFSzAAAAAAAG8AAA Changed row id : AAAFSzAAAAAAAG8AAA 

El problema fue que me registré en la base de datos muchas veces. La solución fue verificar el usuario registrado para los eventos de cambio antes de registrarse.

Lo comprobé con esta consulta:

 select TABLE_NAME from USER_CHANGE_NOTIFICATION_REGS 

El punto es que, de acuerdo con https://docs.oracle.com/cd/E18283_01/appdev.112/e13995/oracle/jdbc/OracleConnection.html, usted es responsable de liberar su DatabaseChangeNotification al anular el registro de su conexión. De lo contrario, como se describe “El registro continuará activo después de que se cierre esta conexión. Debe anular el registro explícito para destruirlo en el servidor y liberar los recursos en el controlador”.

Lo que significa que si prueba su código de muestra y lo mata, sus registros permanecerán en el servidor y recibirá notificaciones adicionales después de registrar la siguiente DatabaseChangeNotification. Desafortunadamente, todavía no me he dado cuenta de que puedo volver a conectar con el registro en vivo. Necesitaría estar registrado para eso, pero aún no tengo idea de cómo obtenerlo de OracleConnection.

Como ya se dijo, usted es responsable de liberar su DatabaseChangeNotification anulándolas en su conexión. En mi caso, por alguna razón, estoy haciendo esto cuando el servidor de aplicaciones se inicia en Tomcat. Primero estoy lanzando registros antiguos y luego creo nuevos. Creo que puedo reutilizar el registro anterior, pero de todos modos, no.

Estoy haciendo algo como esto:

Esta consulta devuelve registros existentes. Debe iniciar sesión como el mismo usuario que registró la notificación.

 SELECT REGID, CALLBACK FROM USER_CHANGE_NOTIFICATION_REGS 

luego el código como este pseudo código anula el registro de la notificación.

connection.unregisterDatabaseChangeNotification (REGID, CALLBACK) esto se llama para cada fila devuelta por la consulta.

Algo más que encontré es que cuando actualizo una fila usando PL / SQL Developer usando el editor incluido, recibo múltiples notificaciones para la misma actualización. Cuando uso sql para actualizar la fila, recibo una notificación como se esperaba. No sé por qué sucede esto, pero está sucediendo.

Por lo que he leído hasta ahora, también estoy un poco confundido.

¿Ha intentado imprimir la transactionId para ver si, de hecho, se está generando un evento único?

 System.out.println("DCE : regId="+dce.getRegristrationId()+"; transactionId="+dce.getTransactionId()); 

Los oyentes con los que he estado trabajando para JMS requieren un reconocimiento, pero no veo nada de ese tipo en los documentos de Oracle ( http://docs.oracle.com/cd/B28359_01/java.111/b31224/ dbmgmnt.htm ).

También encontré que podría perturbar el caché de Oracle si el tratamiento del evento no se ha desarrollado, pero parece que ya lo estás haciendo …

¡Buena suerte!

Obtendrá un evento por cada confirmación que modifique una de las tablas que está usando en su consulta (en su ejemplo de código solo una tabla llamada “EJEMPLO”). Piénselo como “notificación de cambio de tabla” TCN. En otras palabras, puede obtener muchos falsos positivos porque solo está interesado en una fila, pero se le notificará si se cambian otras filas. Depende de usted filtrar los eventos. Esta es la razón por la que esta característica solo debe usarse para leer principalmente tablas.

En 11gR2, Oracle mejoró este mecanismo de notificación para permitir una notificación más fina llamada “Notificación de cambio de consulta”. Esta vez, solo se le notificarán los cambios que afecten los resultados de su consulta. Hay una opción que debe activarse para habilitar QCN en lugar de TCN. Tenga en cuenta que es posible que el servidor no siempre pueda habilitar QCN. Si la consulta es demasiado compleja, volverá a TCN.

Por favor, modifique el controlador de eventos de la siguiente manera:

 public void onDatabaseChangeNotification(DatabaseChangeEvent dce) { if (e.getRegId() == dcr.getRegId()) System.out.println("Changed row id : "+dce.getTableChangeDescription()[0].getRowChangeDescription()[0].getRowid().stringValue()); } 

Corrección de error tipográfico:

Por favor, modifique el controlador de eventos de la siguiente manera:

 public void onDatabaseChangeNotification(DatabaseChangeEvent dce) { if (dce.getRegId() == dcr.getRegId()) System.out.println("Changed row id : "+dce.getTableChangeDescription()[0].getRowChangeDescription()[0].getRowid().stringValue()); }