2017-03-17 27 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'),這種方式我確信觸發器是由來自此實例的動作創建的