gpt4 book ai didi

jsf - 使用 JSF/Java EE 从数据库实时更新

转载 作者:行者123 更新时间:2023-12-03 09:19:02 36 4
gpt4 key购买 nike

我在以下环境中运行了一个应用程序。

  • GlassFish Server 4.0
  • JSF 2.2.8-02
  • PrimeFaces 5.1 最终版
  • PrimeFaces 扩展 2.1.0
  • OmniFaces 1.8.1
  • EclipseLink 2.5.2 具有 JPA 2.1
  • MySQL 5.6.11
  • JDK-7u11

  • 有几个公共(public)页面是从数据库中延迟加载的。一些 CSS 菜单显示在模板页面的标题上,例如显示类别/子类别的特色、最畅销、新到货等产品。

    CSS 菜单是根据数据库中的各种产品类别从数据库动态填充的。

    这些菜单在每个页面加载时都填充,这是完全不必要的。其中一些菜单需要复杂/昂贵的 JPA 标准查询。

    目前,填充这些菜单的 JSF 托管 bean 是 View 范围的。它们都应该是应用程序范围的,仅在应用程序启动时加载一次,并且仅在相应数据库表(类别/子类别/产品等)中的某些内容更新/更改时才更新。

    我尝试了解 WebSokets(以前从未尝试过,对 WebSokets 完全陌生),例如 thisthis .它们在 GlassFish 4.0 上运行良好,但不涉及数据库。我仍然无法正确理解 WebSokets 的工作原理。特别是当涉及到数据库时。

    在这种情况下,当某些内容更新/删除/添加到相应的数据库表时,如何通知关联的客户端并使用数据库中的最新值更新上述 CSS 菜单?

    一个简单的例子会很棒。

    最佳答案

    前言

    在这个答案中,我将假设以下内容:

  • 您对使用 <p:push> 不感兴趣(我将在中间留下确切原因,您至少对使用新的 Java EE 7/JSR356 WebSocket API 感兴趣)。
  • 您想要一个应用程序范围的推送(即所有用户一次获得相同的推送消息;因此您对 session 或查看范围推送不感兴趣)。
  • 您想直接从(MySQL)数据库端调用推送(因此您对使用实体监听器从 JPA 端调用推送不感兴趣)。 编辑 : 无论如何我都会涵盖这两个步骤。步骤 3a 描述 DB 触发器,步骤 3b 描述 JPA 触发器。要么使用它们,要么使用它们,不要同时使用它们!



  • 1. 创建一个 WebSocket 端点

    先创建一个 @ServerEndpoint 基本上将所有 websocket session 收集到应用程序范围内的类。请注意,在此特定示例中,这只能是 static因为每个 websocket session 基本上都有自己的 @ServerEndpoint实例(它们不同于 servlet,因此是无状态的)。
    @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 session (即应用程序范围推送)。请注意,此消息也可以是 JSON 字符串。

    如果您打算将它们显式存储在应用程序范围(或(HTTP) session 范围)中,那么您可以使用 ServletAwareConfig示例在 this answer为了那个原因。你知道, ServletContext属性映射到 ExternalContext#getApplicationMap()在 JSF 中(和 HttpSession 属性映射到 ExternalContext#getSessionMap() )。

    2.在客户端打开WebSocket并监听

    使用这段 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) 解析)时,更多的动态是可能的。

    3a.从 DB 端触发 WebSocket 推送

    现在我们需要触发命令 Push.sendAll("updateMenu")从数据库端。让数据库在 Web 服务上触发 HTTP 请求的最简单方法之一。一个普通的 servlet 足以充当 Web 服务:
    @WebServlet("/push-update-menu")
    public class PushUpdateMenu extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    Push.sendAll("updateMenu");
    }

    }

    如有必要,您当然有机会根据请求参数或路径信息参数化推送消息。如果允许调用者调用此 servlet,请不要忘记执行安全检查,否则世界上除 DB 本身之外的任何其他人都可以调用它。例如,您可以检查调用方的 IP 地址,如果数据库服务器和 Web 服务器在同一台机器上运行,这会很方便。

    为了让 DB 在该 servlet 上触发 HTTP 请求,您需要创建一个可重用的存储过程,它基本上调用操作系统特定的命令来执行 HTTP GET 请求,例如 curl . MySQL 本身不支持执行特定于操作系统的命令,因此您需要先为此安装用户定义函数 (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();

    3b.或者从 JPA 端触发 WebSocket 推送

    如果您的要求/情况仅允许监听 JPA 实体更改事件,因此对 DB 的外部更改会执行 不是 需要覆盖,那么你可以 而不是 步骤 3a 中描述的 DB 触发器也仅使用 JPA 实体更改监听器。您可以通过 @EntityListeners 注册 @Entity上的注释类(class):
    @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 项目应该有 no single对网络项目的依赖。在这种情况下,您最好启动 CDI Event取而代之的是 Web 项目可以 @Observes .
    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));
    }

    }

    (注意结果;在当前版本 (4.1/8.2) 中,GlassFish 和 WildFly 中注入(inject) CDI Event 均已损坏;解决方法改为通过 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");
    }

    }

    更新 :2016 年 4 月 1 日(上述答案半年后), OmniFaces在 2.3 版中引入 <o:socket> 这应该使这一切不那么迂回。即将发布的 JSF 2.3 <f:websocket>主要基于 <o:socket> .另见 How can server push asynchronous changes to a HTML page created by JSF?

    关于jsf - 使用 JSF/Java EE 从数据库实时更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25947790/

    36 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com