>

다음 환경에서 하나의 응용 프로그램을 실행하고 있습니다.

  • GlassFish Server 4.0
  • JSF 2.2.8-02
  • PrimeFaces 5.1 파이널
  • PrimeFaces Extension 2.1.0
  • OmniFaces 1.8.1
  • JPA 2.1을 사용하는 EclipseLink 2.5.2
  • MySQL 5.6.11
  • JDK-7u11

데이터베이스에서 느리게로드되는 여러 공개 페이지가 있습니다. 카테고리/하위 카테고리 별 추천, 인기 판매자, 새로운 도착 등의 제품 표시와 같은 몇 가지 CSS 메뉴가 템플릿 페이지의 헤더에 표시됩니다.

CSS 메뉴는 데이터베이스의 다양한 제품 카테고리에 따라 데이터베이스에서 동적으로 채워집니다.

이 메뉴는 페이지가로드 될 때마다 채워 지므로 완전히 필요하지 않습니다. 이러한 메뉴 중 일부는 복잡하고 비싼 JPA 기준 쿼리가 필요합니다.

현재 이러한 메뉴를 채우는 JSF 관리 Bean은보기 범위가 있습니다. 이들은 모두 응용 프로그램 범위를 지정하고 응용 프로그램 시작시 한 번만로드해야하며 해당 데이터베이스 테이블 (범주/하위 범주/제품 등)의 내용이 업데이트/변경 될 때만 업데이트해야합니다.

저는 이 및 this . 그들은 GlassFish 4.0에서 잘 작동했지만 데이터베이스는 포함하지 않았습니다. 여전히 WebSoket이 작동하는 방식을 제대로 이해할 수 없습니다. 특히 데이터베이스가 관련된 경우

이 시나리오에서는 해당 데이터베이스 테이블에 무언가가 업데이트/삭제/추가 될 때 관련 클라이언트에 알리고 위에서 언급 한 CSS 메뉴를 데이터베이스의 최신 값으로 업데이트하는 방법은 무엇입니까?

간단한 예가 좋을 것입니다.


  • 답변 # 1

    서문

    이 답변에서는 다음을 가정합니다.

    <p:push> 사용에 관심이 없습니다.  (중간에 정확한 이유를 남겨두고, 적어도 새로운 Java EE 7/JSR356 WebSocket API 사용에 관심이 있습니다)

    애플리케이션 범위 푸시를 원합니다 (즉, 모든 사용자가 한 번에 동일한 푸시 메시지를 수신하므로 세션에 관심이 없거나 범위 푸시를 보지 않음)

    (MySQL) DB 측에서 직접 푸시를 호출하려고합니다 (따라서 엔티티 리스너를 사용하여 JPA 측에서 푸시를 호출하는 데 관심이 없습니다).수정: 어쨌든 두 단계를 모두 다룰 것입니다. 3a 단계는 DB 트리거를 설명하고 3b 단계는 JPA 트리거를 설명합니다. 둘 다 사용하거나 둘 다 사용하지 마십시오!

    <시간>


    1. WebSocket 엔드 포인트 생성

    먼저 @ServerEndpoint 를 만드십시오  기본적으로 모든 웹 소켓 세션을 응용 프로그램 전체 세트로 수집합니다. 이 특정 예에서는 static 만 될 수 있습니다.  모든 웹 소켓 세션은 기본적으로 자체 @ServerEndpoint 를 얻으므로  예를 들어 서블릿과 달리 상태 비 저장입니다.

    @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() 가 있습니다  주어진 메시지를 열려있는 모든 웹 소켓 세션 (예 : 응용 프로그램 범위 푸시)으로 보냅니다. 이 메시지는 JSON 문자열 일 수도 있습니다.

    애플리케이션 범위 (또는 (HTTP) 세션 범위)에 명시 적으로 저장하려는 경우 ServletAwareConfig 를 사용할 수 있습니다.  이에 대한이 답변의 예입니다. 알다시피, ServletContext  속성이 ExternalContext#getApplicationMap() 에 매핑  JSF (및 HttpSession )  속성이 ExternalContext#getSessionMap() 에 매핑 ).


    2. 클라이언트 측에서 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") 명령을 실행해야합니다.  DB 쪽에서. DB가 웹 서비스에서 HTTP 요청을 시작하도록하는 가장 간단한 방법 중 하나입니다. 평범한 바닐라 서블릿은 웹 서비스처럼 행동하기에 충분합니다.

    @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 서버와 웹 서버가 모두 같은 컴퓨터에서 실행되는 경우에 편리합니다.

    DB가 해당 서블릿에서 HTTP 요청을 시작하게하려면 재사용 가능한 저장 프로 시저를 작성해야합니다.이 저장 프로시 저는 기본적으로 운영 체제 별 명령을 호출하여 HTTP GET 요청을 실행합니다 (예 : 와이즈 비즈 . MySQL은 기본적으로 OS 특정 명령 실행을 지원하지 않으므로 먼저 사용자 정의 함수 (UDF)를 설치해야합니다. mysqludf.org에서 SYS가 관심을 갖고있는 많은 것을 찾을 수 있습니다. 그것은 curl 를 포함  우리가 필요로하는 기능. 설치가 완료되면 MySQL에서 다음 저장 프로 시저를 만듭니다.

    sys_exec()
    
    

    이제 삽입/업데이트/삭제 트리거를 생성하여이를 호출 할 수 있습니다 (테이블 이름이 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();
    
    


    3b.또는JPA 측에서 WebSocket 푸시를 트리거합니다

    요구 사항/상황이 JPA 엔터티 변경 이벤트 만 수신 할 수 있고 따라서 DB에 대한 외부 변경 사항이없을경우 적용 할 필요가없는 경우대신>3a 단계에서 설명한대로 DB 트리거는 JPA 엔티티 변경 리스너 만 사용합니다. CREATE TRIGGER after_menu_delete AFTER DELETE ON menu FOR EACH ROW CALL menu_push(); 를 통해 등록 할 수 있습니다   @EntityListeners 에 주석  수업 :

    @Entity
    
    

    모든 프로젝트 (EJB/JPA/JSF)가 동일한 프로젝트에 함께 포함 된 단일 웹 프로파일 프로젝트를 사용하는 경우 @Entity @EntityListeners(MenuChangeListener.class) public class Menu { // ... } 를 직접 호출 할 수 있습니다.  거기에.

    Push.sendAll("updateMenu")
    
    
    그러나 "엔터프라이즈"프로젝트에서 서비스 계층 코드 (EJB/JPA/etc)는 일반적으로 EJB 프로젝트에서 분리되는 반면 웹 계층 코드 (JSF/Servlets/WebSocket/etc)는 웹 프로젝트에 유지됩니다. EJB 프로젝트는 웹 프로젝트에 대한 단일 종속성이 없어야합니다. 이 경우 CDI public class MenuChangeListener { @PostPersist @PostUpdate @PostRemove public void onChange(Menu menu) { Push.sendAll("updateMenu"); } } 를 해고하는 것이 좋습니다.  대신 웹 프로젝트가 Event 할 수있는 .

    @Observes
    
    

    (결과에 주목;CDI 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에서 모두 손상되었습니다. 해결 방법은 Event 를 통해 이벤트를 시작합니다.  대신;그래도 문제가 해결되지 않으면 CDI 1.1 대안은 BeanManager 입니다. )

    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;
        }
    }
    
    
    <시간>

    업데이트: 2016 년 4 월 1 일 (상기 답변 후 1 년 반) OmniFaces는 2.3 버전 @ApplicationScoped public class Application { public void onMenuChange(@Observes MenuChangeEvent event) { Push.sendAll("updateMenu"); } } 를 도입했습니다.  이 모든 것을 덜 회로 화해야합니다. 다가오는 JSF 2.3 Wyzwyz  주로 <o:socket> 를 기반으로합니다. . JSF로 작성된 HTML 페이지에 서버가 비동기 변경 사항을 푸시하는 방법도 참조하십시오.

  • 답변 # 2

    Primefaces 및 Java EE 7을 사용하므로 구현하기 쉬워야합니다.

    프라임 페이스 푸시 사용 (예 : http://www.primefaces.org/showcase/push/notify.xhtml)

    Websocket 엔드 포인트를 수신하는보기 작성

    데이터베이스 변경시 CDI 이벤트를 생성하는 데이터베이스 리스너 작성

    이벤트의 페이로드는 최신 데이터의 델타이거나 정보를 업데이트 할 수 있습니다

    Websocket을 통해 CDI 이벤트를 모든 클라이언트에게 전파

    데이터를 업데이트하는 클라이언트

    이것이 도움이되기를 바랍니다 더 자세한 내용이 필요하면

    안녕

  • 답변 # 3

    PrimeFaces에는 구성 요소를 자동으로 업데이트하는 폴링 기능이 있습니다. 다음 예에서 <f:websocket>   <o:socket> 에 의해 3 초마다 자동 업데이트됩니다 .

    와이즈 비즈 <h:outputText> 와 같은 리스너 메소드 작성  메뉴 데이터를 선택하십시오. 와이즈 비즈  메뉴 구성 요소가 자동으로 업데이트됩니다.

    <p:poll>
    
    

    How to notify the associated clients and update the above-mentioned CSS menus with the latest values from the database?

    process()

관련 자료

  • 이전 mongodb - 동일한 문서의 데이터를 사용하여 문서 업데이트
  • 다음 r - order () 함수 이해