在外部Postgres数据库更新时触发java事件

时间:2013-05-11 16:45:51

标签: java jsp postgresql web-applications

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

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

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

现在出现问题:有一个远程服务可以直接访问数据库并更新选项表中的某些字段。我对这段代码没有控制权。

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

postgresql(Postgres trigger to update Java cache)提供的 LISTEN - NOTIFY 功能在我看来是实现此目标的最佳方式。 在这个主题之后,我正在尝试一个简单的倾听者并根据我的需要“适应”(Sample of code in Postgress Documentation)。

在@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());
            }
        }
    }
}

在Options Class中,我使用以下方法手动启动监听器:

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

最后

Options.startExternalChangesListener(options, 5000);

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

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

2 个答案:

答案 0 :(得分: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());
        } 
    }
}

答案 1 :(得分:0)

如果听众未收到预期通知,则此处的问题是您应该仔细整理的问题。注意我已经包含了为完整性而已经完成的步骤。

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

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

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

  4. 此时很难说问题出在哪里。但是,如果你按顺序解决这些问题(听起来你已经解决了#1并且可以从#2开始)那么你应该能够在不太长的时间内找到问题。