2017-03-17 29 views
0

我尝试创建客户端 - >数据库通知/更新系统。客户端是用Java/Hibernate编写的。通知客户本人使用触发器:基于通知的实时PostgreSQL客户端更新

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”来确定“THIS客户端”是否进行了更改(当然在这种情况下客户端不应该更新)或其他人(在此情况下,更新应适用)

编辑 - - -

我想刷新这个话题,因为它没有得到解决。调用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。

任何想法?

回答

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 
+0

将链接添加到https://jdbc.postgresql.org/documentation/head/listennotify.html这个答案。 –

+0

确定它工作正常,但是有没有办法避免调用: SELECT pg_backend_pid();我每次收到通知时都会收到 ? –

+0

如果您的会话在收听活动时持续存在,那么您可以获得一次pid并将其存储在某个变量中。如果你不知道它是否持续,那么我会通过存储数组中的所有pid并检查其内容或者简单地存储'oldPid'和比较新鲜的pid来测试它。 –

0

我花了一段时间来打磨的解决方案,但我发现真的很酷之一。我希望独立于我的软件的一个实例的已打开会话的数量,并确保软件实例能够正确确定触发器是否来自此实例(即使一个实例可能打开多个会话,以及同一台PC可以打开多个实例)。这里,它是:

  1. 当登录到我使用的应用程序名称来命名会话的应用记录(数据库中)的数据库:JDBC:在PostgreSQL://本地主机:5432 /应用程序名称= based_on_run_time_hash,你可以看到这在触发功能使用SELECT * from pg_stat_activity

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软件实例接收触发它比较本地应用程序名称是否相同,在JSON发送(“SE ssion'),这种方式我确信触发器是由来自此实例的动作创建的