【问题标题】:Real-Time PostgreSQL client updates based on notification基于通知的实时 PostgreSQL 客户端更新
【发布时间】:2018-02-18 17:47:37
【问题描述】:

我尝试创建客户端 -> 数据库通知/更新系统。客户端是用 Java / Hibernate 编写的。为了通知客户,我使用了触发器:

CREATE OR REPLACE FUNCTION notifyUsers() RETURNS TRIGGER AS $$
DECLARE 
    data integer;
    notification json;

BEGIN

    IF (TG_OP = 'DELETE') THEN
        data = OLD.id;
    ELSE
        data = NEW.id;
    END IF;

    -- Contruct the notification
    notification = json_build_object(
                      'table',TG_TABLE_NAME,
                      'action', TG_OP,
                      'id', data);


    -- Execute pg_notify(channel, notification)
    PERFORM pg_notify('events',notification::text);

    -- Result is ignored since this is an AFTER trigger
    RETURN NULL; 
END;
$$ LANGUAGE 'plpgsql';


CREATE TRIGGER notifyUsersAccountData AFTER INSERT OR UPDATE OR DELETE ON document FOR EACH ROW EXECUTE PROCEDURE notifyUsers();

但是当客户端收到通知时,我无法确定是谁触发了它。 我可以发送任何其他参数来确定谁触发它(session_id?在我的应用程序中有自定义用户,但我想避免发送 User.id,因为通过 2 台 PC 登录同一用户会破坏我的通知/更新系统)

在收到通知后,客户端更新值,需要“会话 id”来确定是“这个客户端”进行了更改(当然在这种情况下客户端不应该更新)还是其他人(在这种情况下应该应用更新)

编辑 - - -

我想刷新这个话题,因为它没有解决。调用 pg_backend_pid 的解决方案有时会中断,导致“执行 SQL 查询的调用者的 PID”与 PGNotification.getPid 不同。代码:

public int getSessionPid() {
int lResult = -1;
try
{
    connect();
    Session session = _sessionFactory.getCurrentSession();
    Transaction lTransaction = session.beginTransaction();
    SQLQuery lQuery = session.createSQLQuery("SELECT pg_backend_pid()");

    List lResultQuery = lQuery.list();
    lResult = Integer.parseInt(lResultQuery.get(0).toString());
    lTransaction.commit();
    }catch(org.hibernate.SessionException e)
    {
        e.printStackTrace();
    }

    return lResult;
}

以及比较出现的代码:

int lPid = Facade.databaseConnector.warehouse.getSessionPid();
for(int i = 0; i < notifications.length; ++i)
{
    if(lPid == notifications[i].getPID())
...

此外,我在表中看到当前后端连接可见select * from pg_stat_activity,每个我的程序实例有 3 个连接。

  1. SELECT 1 - 只是为了更新通知,因为没有“接触”数据库,通知不会被触发
  2. 一个连接与休眠有关
  3. 一个连接与数据库中的 SQL 查询相关(如插入/更新/删除行等...)

所有后端连接在 pg_stat_activity 中都有不同的端口和 PID。

有什么想法吗?

【问题讨论】:

    标签: java sql postgresql hibernate


    【解决方案1】:

    pg_notify 的 PostgreSQL 文档似乎可以解决这个问题。

    执行 NOTIFY 的客户端通常会监听同一通知通道本身。在这种情况下,它会返回一个通知事件,就像所有其他监听会话一样。根据应用程序逻辑,这可能会导致无用的工作,例如,读取数据库表以查找该会话刚刚写出的相同更新。通过注意通知会话的服务器进程 PID(在通知事件消息中提供)是否与自己会话的 PID(可从 libpq 获得)相同,可以避免此类额外工作。当它们相同时,通知事件是自己的工作反弹,可以忽略。

    所以你的客户端得到这样的有效载荷:

    SELECT pg_notify('foo', 'payload');
    Asynchronous notification of 'foo' received from backend pid 13976
        Data: payload
    

    您可以从中解析出该 pid。我猜它有点像 session_id。

    您可以像这样获取当前会话 pid:

    SELECT pg_backend_pid();
    13976
    

    【讨论】:

    • 最好在此答案中添加指向jdbc.postgresql.org/documentation/head/listennotify.html 的链接。
    • 好吧,它运作良好,但有没有办法避免调用:SELECT pg_backend_pid();每次收到通知?
    • 如果您的会话在侦听事件时仍然存在,那么您可以获取一次 pid 并将其存储在某个变量中。如果您不知道它是否持续存在,那么我将通过将所有 pid 存储在数组中并检查其内容或仅存储 oldPid 并与新的比较来测试它。
    【解决方案2】:

    我花了一些时间来完善解决方案,但我发现了一个非常酷的解决方案。我想独立于我的软件的一个实例中打开的会话数量,并确保软件实例正确确定触发器是否来自该实例(即使一个实例可能打开多个会话,并且在同一台 PC 可以打开多个实例)。这里是:

    1. 登录到数据库时,我使用 Application_name 来命名会话中的应用记录(在数据库内):jdbc:postgresql://localhost:5432/?ApplicationName=based_on_run_time_hash,您可以使用 SELECT * from pg_stat_activity 看到这一点
    2. 在触发功能中我有

    EXECUTE 'SELECT application_name from pg_stat_activity where pid IN (SELECT pg_backend_pid())' INTO session_app_name; notification = json_build_object( 'session', session_app_name, ...);

    当 PC 软件实例收到触发器时,它会比较本地 Application_name 是否与在 json ('session') 中发送的相同,这样我可以确定触发器是由来自该实例的操作创建的

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-04
      • 2015-02-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多