次の環境で1つのアプリケーションを実行しています。
データベースから遅延ロードされるいくつかの公開ページがあります。いくつかのCSSメニューが、カテゴリ/サブカテゴリごとのおすすめ商品、トップセラー、新着商品などのテンプレートページのヘッダーに表示されます。
CSSメニューは、データベース内の製品のさまざまなカテゴリに基づいて、データベースから動的に読み込まれます。
これらのメニューは、ページを読み込むたびに読み込まれますが、これはまったく不要です。これらのメニューの一部は、複雑で高価なJPA基準クエリを必要とします。
現在、これらのメニューに表示されるJSF管理対象Beanは、ビュースコープです。これらはすべてアプリケーションスコープであり、アプリケーションの起動時に一度だけ読み込まれ、対応するデータベーステーブル(カテゴリ/サブカテゴリ/製品など)の何かが更新/変更された場合にのみ更新されます。
this や this のように、WebSoketを理解しようと試みました(これまでに試したことはなく、WebSoketにはまったく新しいものです)。 GlassFish 4.0では正常に機能しましたが、データベースは関係しません。 WebSoketがどのように機能するのか、まだ適切に理解できません。特にデータベースが関係している場合。
このシナリオでは、対応するデータベーステーブルに何かが更新/削除/追加されたときに、関連付けられたクライアントに通知し、データベースの最新の値で上記のCSSメニューを更新する方法は?
簡単な例は素晴らしいでしょう。
この回答では、次のことを想定します。
<p:Push>
_の使用には興味がありません(正確な理由は途中で説明しますが、少なくとも新しいJava EE 7/JSR356 WebSocket APIの使用に興味があります。 )。最初に _@ServerEndpoint
_ クラスを作成します。このクラスは、基本的にすべてのwebsocketセッションをアプリケーション全体のセットに収集します。この特定の例では、すべてのwebsocketセッションが基本的に独自の_@ServerEndpoint
_インスタンスを取得するため、これはstatic
にしかならないことに注意してください(サーブレットとは異なり、ステートレスです)。
_@ServerEndpoint("/Push")
public class Push {
private static final Set<Session> SESSIONS = ConcurrentHashMap.newKeySet();
@OnOpen
public void onOpen(Session session) {
SESSIONS.add(session);
}
@OnClose
public void onClose(Session session) {
SESSIONS.remove(session);
}
public static void sendAll(String text) {
synchronized (SESSIONS) {
for (Session session : SESSIONS) {
if (session.isOpen()) {
session.getAsyncRemote().sendText(text);
}
}
}
}
}
_
上記の例には、追加のメソッドsendAll()
があります。このメソッドは、指定されたメッセージを開いているすべてのWebsocketセッション(つまり、アプリケーションスコープのプッシュ)に送信します。このメッセージは、JSON文字列でも非常に適切です。
アプリケーションスコープ(または(HTTP)セッションスコープ)に明示的に保存する場合は、そのために this answer のServletAwareConfig
の例を使用できます。ご存知のように、ServletContext
属性はJSFのExternalContext#getApplicationMap()
にマップされます(およびHttpSession
属性はExternalContext#getSessionMap()
にマップされます)。
このJavaScriptを使用して、WebSocketを開いてリッスンします。
_if (window.WebSocket) {
var ws = new WebSocket("ws://example.com/contextname/Push");
ws.onmessage = function(event) {
var text = event.data;
console.log(text);
};
}
else {
// Bad luck. Browser doesn't support it. Consider falling back to long polling.
// See http://caniuse.com/websockets for an overview of supported browsers.
// There exist jQuery WebSocket plugins with transparent fallback.
}
_
現在のところ、プッシュされたテキストを記録するだけです。このテキストをメニューコンポーネントを更新するための指示として使用したいと思います。そのためには、追加の_<p:remoteCommand>
_が必要です。
_<h:form>
<p:remoteCommand name="updateMenu" update=":menu" />
</h:form>
_
Push.sendAll("updateMenu")
によってJS関数名をテキストとして送信しているとしたら、次のように解釈してトリガーできます。
_ ws.onmessage = function(event) {
var functionName = event.data;
if (window[functionName]) {
window[functionName]();
}
};
_
繰り返しますが、JSON文字列をメッセージとして使用する場合($.parseJSON(event.data)
で解析できます)、より多くのダイナミクスが可能です。
ここで、DB側からPush.sendAll("updateMenu")
コマンドをトリガーする必要があります。 DBがWebサービスでHTTPリクエストを実行できるようにする最も簡単な方法の1つ。単純なVanillaサーブレットは、Webサービスのように振る舞うには十分です:
_@WebServlet("/Push-update-menu")
public class PushUpdateMenu extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Push.sendAll("updateMenu");
}
}
_
もちろん、必要に応じて、要求パラメーターまたはパス情報に基づいてプッシュメッセージをパラメーター化する機会があります。呼び出し元がこのサーブレットの呼び出しを許可されている場合は、セキュリティチェックを実行することを忘れないでください。そうしないと、DB以外の世界の誰かがそれを呼び出すことができます。たとえば、呼び出し元のIPアドレスを確認できます。これは、DBサーバーとWebサーバーの両方が同じマシンで実行されている場合に便利です。
DBがそのサーブレットでHTTPリクエストを起動できるようにするには、基本的にオペレーティングシステム固有のコマンドを呼び出してHTTP GETリクエストを実行する再利用可能なストアドプロシージャを作成する必要があります。 curl
。 MySQLはOS固有のコマンドの実行をネイティブでサポートしていないため、そのためにまずユーザー定義関数(UDF)をインストールする必要があります。 mysqludf.org で、多くの [〜#〜] sys [〜#〜] が興味深いものを見つけることができます。必要なsys_exec()
関数が含まれています。インストールしたら、MySQLで次のストアドプロシージャを作成します。
_DELIMITER //
CREATE PROCEDURE menu_Push()
BEGIN
SET @result = sys_exec('curl http://example.com/contextname/Push-update-menu');
END //
DELIMITER ;
_
これで、それを呼び出す挿入/更新/削除トリガーを作成できます(テーブル名の名前はmenu
と仮定):
_CREATE TRIGGER after_menu_insert
AFTER INSERT ON menu
FOR EACH ROW CALL menu_Push();
_
_CREATE TRIGGER after_menu_update
AFTER UPDATE ON menu
FOR EACH ROW CALL menu_Push();
_
_CREATE TRIGGER after_menu_delete
AFTER DELETE ON menu
FOR EACH ROW CALL menu_Push();
_
要件/状況でJPAエンティティ変更イベントのみをリッスンできる場合、DBへの外部変更はnotをカバーする必要があります。 の代わりに、ステップ3aで説明したDBトリガーもJPAエンティティ変更リスナーを使用します。 _@EntityListeners
_クラスの_@Entity
_アノテーションを介して登録できます:
_@Entity
@EntityListeners(MenuChangeListener.class)
public class Menu {
// ...
}
_
単一のWebプロファイルプロジェクトを使用して、すべて(EJB/JPA/JSF)が同じプロジェクトで一緒にスローされる場合、Push.sendAll("updateMenu")
を直接呼び出すことができます。
_public class MenuChangeListener {
@PostPersist
@PostUpdate
@PostRemove
public void onChange(Menu menu) {
Push.sendAll("updateMenu");
}
}
_
ただし、「エンタープライズ」プロジェクトでは、通常、サービスレイヤーコード(EJB/JPA/etc)はEJBプロジェクトで分離され、Webレイヤーコード(JSF/Servlets/WebSocket/etc)はWebプロジェクトに保持されます。 EJBプロジェクトには、Webプロジェクトへの 単一なし 依存関係が必要です。その場合、代わりにWebプロジェクトが_@Observes
_できるCDI Event
を実行する方がよいでしょう。
_public class MenuChangeListener {
// Outcommented because it's broken in current GF/WF versions.
// @Inject
// private Event<MenuChangeEvent> event;
@Inject
private BeanManager beanManager;
@PostPersist
@PostUpdate
@PostRemove
public void onChange(Menu menu) {
// Outcommented because it's broken in current GF/WF versions.
// event.fire(new MenuChangeEvent(menu));
beanManager.fireEvent(new MenuChangeEvent(menu));
}
}
_
(結果に注意してください; CDI Event
の注入は、現在のバージョン(4.1/8.2)のGlassFishとWildFlyの両方で壊れています。回避策は、代わりにBeanManager
を介してイベントを起動します。 CDI 1.1の代替手段はCDI.current().getBeanManager().fireEvent(new MenuChangeEvent(menu))
)
_public class MenuChangeEvent {
private Menu menu;
public MenuChangeEvent(Menu menu) {
this.menu = menu;
}
public Menu getMenu() {
return menu;
}
}
_
そして、Webプロジェクトで:
_@ApplicationScoped
public class Application {
public void onMenuChange(@Observes MenuChangeEvent event) {
Push.sendAll("updateMenu");
}
}
_
Update:2016年4月1日(上記の回答から半年後)、 OmniFaces バージョン2.3で導入された _<o:socket>
_ これにより、すべての回路がより少なくなります。今後のJSF 2.3 _<f:websocket>
_は、主に_<o:socket>
_に基づいています。 JSFによって作成されたHTMLページに非同期の変更をサーバーでプッシュする方法 も参照してください。
PrimefacesとJava EE 7を使用しているので、簡単に実装できるはずです。
primefaces Pushを使用します(例はこちら http://www.primefaces.org/showcase/Push/notify.xhtml )
これが役立つことを願っています
よろしく
PrimeFacesには、コンポーネントを自動的に更新するポーリング機能があります。次の例では、_<h:outputText>
_は_<p:poll>
_によって3秒ごとに自動更新されます。
関連するクライアントに通知し、データベースからの最新の値で上記のCSSメニューを更新する方法は?
process()
などのリスナーメソッドを作成して、メニューデータを選択します。 _<p:poll>
_は、メニューコンポーネントを自動更新します。
_<h:form>
<h:outputText id="count"
value="#{AutoCountBean.count}"/> <!-- Replace your menu component-->
<p:poll interval="3" listener="#{AutoCountBean.process}" update="count" />
</h:form>
_
_@ManagedBean
@ViewScoped
public class AutoCountBean implements Serializable {
private int count;
public int getCount() {
return count;
}
public void process() {
number++; //Replace your select data from db.
}
}
_