You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java环境下Oracle数据库变更通知无法生效的问题求助

Oracle 19c Database Change Notification 无法接收通知排查思路

环境信息

  • JDK 11
  • ojdbc10/ojdbc11
  • Oracle 19c 企业版
  • 客户端通过VPN连接数据库

已执行操作

SQL语句

-- 授予变更通知权限
grant change notification to <user>;

-- 创建测试表
CREATE TABLE myuser.Test (TEST VARCHAR2(255));

-- 授予用户权限
GRANT CONNECT, CREATE TABLE, CREATE PROCEDURE, CREATE SEQUENCE TO myuser.test;
GRANT CHANGE NOTIFICATION TO myuser.test;
GRANT EXECUTE ON DBMS_CHANGE_NOTIFICATION TO myuser.test;

Java代码

import java.sql.*;
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 Main  {
   public static void main(String[] args) {
       try {
           // Connect to the database
           Class.forName("oracle.jdbc.driver.OracleDriver");
           //jdbc:oracle:thin:@//HOSTNAME:PORT/SERVICENAME
           OracleConnection conn = (OracleConnection) DriverManager.getConnection("jdbc:oracle:thin:@//<serverIP>:1521/orcl7", "username", "password");


           Properties prop = new Properties();
           prop.setProperty(OracleConnection.DCN_NOTIFY_ROWIDS,"true");
           prop.setProperty(OracleConnection.DCN_BEST_EFFORT,"true");
           prop.setProperty(OracleConnection.DCN_QUERY_CHANGE_NOTIFICATION,"true");
           DatabaseChangeRegistration dcr = conn.registerDatabaseChangeNotification(prop);

           try {
               // add the listenerr:
               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 myuser.test where test='TEST'");
               
               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.unregisterDatabaseChangeNotification(dcr);
                   conn.close();
               } catch (Exception innerex) {
                   innerex.printStackTrace();
               }
           }



       } catch (SQLException e) {
           throw new RuntimeException(e);
       } catch (ClassNotFoundException e) {
           throw new RuntimeException(e);
       }

   }
}

class DCNDemoListener implements DatabaseChangeListener
{
   public void onDatabaseChangeNotification(DatabaseChangeEvent e)
   {
       System.out.println("Caught");
       System.out.println(e.toString());
   }
}

当前问题

  • 程序输出显示注册的表为 TMCDB7.TEMP,而非目标表 myuser.test
  • 数据库端 USER_CHANGE_NOTIFICATION_REGS 可查看到注册记录,但Java客户端无法接收任何变更通知
  • 曾出现ORA-12535错误,现已消失,数据库无其他异常日志
  • 客户端通过VPN连接数据库

已尝试的解决方法

  • 调整ORA-12535超时时间
  • 确认数据库1521端口已白名单放行
  • 使用IP地址而非主机名连接数据库
  • 添加DCN属性(DCN_BEST_EFFORT、NTF_LOCAL_TCP_PORT、NTF_LOCAL_HOST)无效
  • 升级JDK至11,ojdbc升级至ojdbc11
  • 更新监听器队列大小为QUEUESIZE=99

针对性排查思路

1. 修正DCN注册的目标表问题

程序输出注册的是TMCDB7.TEMP,这是核心异常点,直接导致无法接收myuser.test的变更通知:

  • 检查连接用户的默认Schema:若连接用户是TMCDB7,执行select * from myuser.test时需确认用户拥有该表的SELECT权限,可执行GRANT SELECT ON myuser.test TO <连接用户>补充权限
  • 验证表名大小写:Oracle默认将未加双引号的表名转为大写,创建的myuser.Test实际为MYUSER.TEST,代码中myuser.test会自动解析为大写,但需避免因引号导致的表名不匹配
  • 排查查询是否触发临时表:若查询涉及复杂逻辑或权限不足,Oracle可能生成临时表,但此场景下应先确保myuser.test的查询能正常返回(哪怕结果为空),可在代码中添加查询结果打印,确认查询是否正常执行

2. 确保客户端JVM持续运行

当前代码在conn.close()后主线程直接退出,JVM终止导致监听线程无法存活,无法接收通知:

  • 在finally块的conn.close()后添加阻塞逻辑,例如:
    // 阻塞主线程,保持监听线程运行
    synchronized (Main.class) {
        Main.class.wait();
    }
    
    或使用CountDownLatch:
    private static final CountDownLatch latch = new CountDownLatch(1);
    // 在main方法末尾添加
    latch.await();
    

3. 排查VPN环境下的双向网络连通性

DCN是数据库主动向客户端推送通知,需确保数据库能访问客户端的监听端口:

  • 确认NTF_LOCAL_HOST配置的是VPN分配给客户端的内网IP(而非本地环回或局域网IP),确保数据库能通过VPN路由到该IP
  • 确认NTF_LOCAL_TCP_PORT指定的端口已在客户端防火墙开放,且VPN策略允许数据库端发起的连接请求
  • 在数据库服务器上使用telnet <客户端VPN IP> <指定端口>测试连通性,若无法连接需调整VPN路由或防火墙规则

4. 验证数据库端DCN配置与权限

  • 检查数据库初始化参数:确保JOB_QUEUE_PROCESSES值≥1(建议设置为10),DCN依赖后台JOB进程推送通知
  • 查看USER_CHANGE_NOTIFICATION_REGS细节:执行以下SQL确认注册的表、通知类型、状态:
    SELECT regid, table_name, operations, notification_type, status 
    FROM USER_CHANGE_NOTIFICATION_REGS;
    
    若注册的表不是MYUSER.TEST,需重新执行正确的查询关联DCN注册
  • 确认用户权限:除已授予的权限外,确保连接用户拥有ALTER SESSION权限(部分DCN场景需要)

5. 确认ojdbc依赖完整性

JDK11需搭配ojdbc11.jar,且需包含以下依赖包:

  • oraclepki.jar
  • osdt_core.jar
  • osdt_cert.jar
    缺少这些依赖可能导致DCN通知推送失败或网络连接异常

内容的提问来源于stack exchange,提问作者Rajesh Ingole

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 07:12:09