0

描述:我在 linux 上有一个没有任何框架的 java web 应用程序(jsp + tomcat)。我使用 postgresql 作为 DBMS。

我有一个表(options)和一个封装它的 Singleton 类(Options)。Options 实例在应用程序启动时加载并无限期地保留在那里。

如果用户修改选项,则方法(.refreshData())会更新保存在内存中的实例。

现在的麻烦是:有一个远程服务可以直接访问数据库并更新选项表中的一些字段。我无法控制这段代码。

我想在外部服务更新选项表时触发刷新方法。我也知道该服务每天下午 3 点开始一次,但我不知道它什么时候结束。

postgresql 提供的LISTEN - NOTIFY功能(用于更新 Java 缓存的 Postgres 触发器)在我看来是实现这一目标的最优雅的方式。按照这个主题,我正在尝试一个简单的监听器并“适应”我的需要(Postgress 文档中的代码示例)。

在@Craig 建议后编辑:

public class OptionsListener extends Thread {
    private int threadMills = 1000;
    private Connection conn;
    private org.postgresql.PGConnection pgconn;
    private Options optionsInstance;
    private static final String DB_URL;
    private static final String DB_USERNAME;
    private static final String DB_PASSWORD;

    static {
        try {
            Context initContext = new InitialContext();
            Context envContext = (Context) initContext.lookup("java:/comp/env");

            DB_URL = (String) envContext.lookup("application/DB/url");
            DB_USERNAME = (String) envContext.lookup("application/DB/username");
            DB_PASSWORD = (String) envContext.lookup("application/DB/password");
        } catch (NamingException e) {
            throw new RuntimeException(e);
        }
    }

    OptionsListener(Options instance, int threadMillis) {
        optionsInstance = instance;
        this.threadMills = threadMillis;

        try {
            Class.forName("org.postgresql.Driver");
            conn = DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD);
            pgconn = (PGConnection) DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD);

            Statement stmt = conn.createStatement();
            stmt.execute("LISTEN otionsUpdate");
            stmt.close();
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    @Override
    public void run() {
        while (true) {
            Log.addItem("Polling ?");
            try {
                Statement stmt = conn.createStatement();
                ResultSet rs = stmt.executeQuery("SELECT 1");
                rs.close();
                stmt.close();

                PGNotification notifications[] = pgconn.getNotifications();
                if (notifications != null) {
                    Log.addItem("NOTIFY received");
                    optionsInstance.loadDbData();
                }

                Thread.sleep(threadMills); //tempo di attesa in millisecondi
            } catch (Exception e) {
                Log.addItem(getClass().getName() + " " + e.getMessage());
            }
        }
    }
}

在选项类中,我使用此方法手动启动侦听器:

public static void startExternalChangesListener(Options instance, int millis) {
    OptionsListener listener = new OptionsListener(instance, millis);
    listener.start();
}

最后

Options.startExternalChangesListener(options, 5000);

这是我第一次篡改线程...

我创建了一个通知频道的AFTER UPDATE 触发器,并通过 PGAdmin3 对其进行了测试。它就像一个魅力,但java似乎没有注意到......

4

2 回答 2

2

明白了,似乎问题在于不必要的双重连接(名为 pgcon)。

我正在使用jdbc-postgresql 9.2_p1003,它就像一个魅力。

这是最终代码:

OptionsListener(Options instance, int threadMillis) {
    optionsInstance = instance;
    this.threadMills = threadMillis;

    try {
        Class.forName("org.postgresql.Driver");
        conn = DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD);

        Statement stmt = conn.createStatement();
        stmt.execute("SET application_name = 'myapp'; LISTEN optionsupdate");
        stmt.close();
    } catch (SQLException e) {
        Log.addItem(getClass().getName() + " sql error:" + e.getMessage());
    } catch (ClassNotFoundException e) {
        throw new RuntimeException(e.getMessage(), e);
    }
}

@Override
public void run() {
    while (true) {
        try {
            PGNotification notifications[] = ((PGConnection) conn).getNotifications();
            if (notifications != null) {
                optionsInstance.loadDbData();
            }

            Thread.sleep(threadMills); 
        } catch (SQLException sqle) {
            Log.addItem(getClass().getName() + " sql error:" + sqle.getMessage());
        } catch (InterruptedException ie) {
            Log.addItem(getClass().getName() + " thread error: " + ie.getMessage());
        } 
    }
}
于 2014-01-31T13:15:19.087 回答
0

如果听众没有收到预期的通知,那么这里的问题是您应该按顺序仔细整理的问题。请注意,为了完整起见,我包括了您已经完成的步骤。

  1. 是否真的提出了通知?你能用 psql 验证一下吗?如果没有,您需要在那里进行一些故障排除。听起来你已经这样做了,所以到第 2 步。

  2. 另一个会话真的在听吗?数据包窥探器在这里可能会有所帮助,因为您可以看到通知来自数据库。如果它没有在监听,那么您需要确保 LISTEN 命令在您的应用程序中的正确时间运行并从那里进行调试。

  3. 您的应用程序中的轮询是否正确进行?如果您在轮询过程中将警告或通知打印为标准错误,这也很有帮助。

在这一点上,很难说问题出在哪里。但是,如果您按顺序解决这些问题(听起来您已经解决了#1并且可以从#2开始),那么您应该能够在不久的将来找到问题。

于 2013-06-06T06:10:57.687 回答