簡體   English   中英

如何從 websocket 端點訪問反應式華麗實體 Session 管理器? (誇庫斯,叛變,華麗,反應)

[英]How to access reactive Panache Entity Session Manager from a websocket endpoint? (Quarkus, Mutiny, Panache, Reactive)

我正在嘗試構建一個示例應用程序,其中消息可以保存到數據庫,然后當 websocket 與 sessionId 連接時,這些消息保存在下面,它們都被發送出去。

這是我的代碼:

Message.java
---
package org.acme.hibernate.orm.panache;

import java.util.Date;

import javax.persistence.Cacheable;
import javax.persistence.Entity;
import io.quarkus.panache.common.Sort;

import io.quarkus.hibernate.reactive.panache.PanacheEntity;
import io.smallrye.mutiny.Multi;

@Entity
@Cacheable
public class Message extends PanacheEntity {

  public String sessionId;
  public String content;
  public Date timestamp;

  public Message() {
    this.timestamp = new Date();
  }

  public Message(String content, String sessionId) {
    this.content = content;
    this.timestamp = new Date();
    this.sessionId = sessionId;
  }  

  public static Multi<Message> getBySessionId(String sessionId) {
    return stream("sessionId", Sort.by("timestamp"), sessionId);
  }

}

MessageResource.java
---
package org.acme.hibernate.orm.panache;

import java.util.Date;
import java.util.List;

import static javax.ws.rs.core.Response.Status.CREATED;

import javax.inject.Inject;
import javax.transaction.Transactional;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

import io.quarkus.hibernate.reactive.panache.Panache;
import io.smallrye.mutiny.CompositeException;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;


@Path("/message")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class MessageResource {
  
  @GET
  public Uni<List<Message>> list() {
    return Message.listAll();
  }

  @GET
  @Path("/{sessionId}")
  public Multi<Message> listBySessionId(@PathParam("sessionId") String sessionId) {
    return Message.getBySessionId(sessionId);
  }

  @POST
  @Transactional
  public Uni<Response> create(Message message) {
    message.timestamp = new Date();
    message.persist();
    return Panache.withTransaction(message::persist)
      .replaceWith(Response.ok(message).status(CREATED)::build);
  }


  /**
     * Create a HTTP response from an exception.
     *
     * Response Example:
     *
     * <pre>
     * HTTP/1.1 422 Unprocessable Entity
     * Content-Length: 111
     * Content-Type: application/json
     *
     * {
     *     "code": 422,
     *     "error": "Fruit name was not set on request.",
     *     "exceptionType": "javax.ws.rs.WebApplicationException"
     * }
     * </pre>
     */
    @Provider
    public static class ErrorMapper implements ExceptionMapper<Exception> {

        @Inject
        ObjectMapper objectMapper;

        @Override
        public Response toResponse(Exception exception) {

            Throwable throwable = exception;

            int code = 500;
            if (throwable instanceof WebApplicationException) {
                code = ((WebApplicationException) exception).getResponse().getStatus();
            }

            // This is a Mutiny exception and it happens, for example, when we try to insert a new
            // fruit but the name is already in the database
            if (throwable instanceof CompositeException) {
                throwable = ((CompositeException) throwable).getCause();
            }

            ObjectNode exceptionJson = objectMapper.createObjectNode();
            exceptionJson.put("exceptionType", throwable.getClass().getName());
            exceptionJson.put("code", code);

            if (exception.getMessage() != null) {
                exceptionJson.put("error", throwable.getMessage());
            }

            return Response.status(code)
                    .entity(exceptionJson)
                    .build();
        }
      }
}
ReplaySocket.java
---
package org.acme.websockets;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import io.quarkus.hibernate.reactive.panache.PanacheEntityBase;
import io.quarkus.panache.common.Sort;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

import org.acme.hibernate.orm.panache.Message;
import org.acme.hibernate.orm.panache.MessageResource;
import org.jboss.logging.Logger;

import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;

@ServerEndpoint("/replay/{sessionId}")         
@ApplicationScoped
public class ReplaySocket {

  @Inject
  MessageResource messageResource;

  private static final Logger LOG = Logger.getLogger(ReplaySocket.class.getName());
  
  Map<String, Session> sessions = new ConcurrentHashMap<>(); 

  @OnOpen
    public void onOpen(Session session, @PathParam("sessionId") String sessionId) {
        sessions.put(sessionId, session);
        broadcast("Starting");
        replayMessages(sessionId);
    }

    private void replayMessages(String sessionId) {
      Multi<Message> messages = Message.stream("sessionId", Sort.by("timestamp"), sessionId);

      
      messages.subscribe().with(
        message -> broadcast(message.content),
        failure -> System.out.println(failure)
      );
    }

    private void broadcast(String message) {
      sessions.values().forEach(s -> {
          s.getAsyncRemote().sendObject(message, result ->  {
              if (result.getException() != null) {
                  System.out.println("Unable to send message: " + result.getException());
              }
          });
      });
  }

}

當我運行它時,我可以保存並從 MessageResource 端點獲取。 但是,當我嘗試獲取 websocket 中的消息時,我收到此錯誤:

2021-07-13 10:23:57,016 ERROR [org.hib.rea.errors] (vert.x-eventloop-thread-8) failed to execute statement [select message0_.id as id1_0_, message0_.content as content2_0_, message0_.sessionId as sessioni3_0_, message0_.timestamp as timestam4_0_ from Message message0_ where message0_.sessionId=$1 order by message0_.timestamp]
2021-07-13 10:23:57,017 ERROR [org.hib.rea.errors] (vert.x-eventloop-thread-8) could not execute query: java.util.concurrent.CompletionException: java.lang.IllegalStateException: Session/EntityManager is closed
    at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:314)
    at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:319)
    at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1081)
    at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506)
    at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2073)
    at io.vertx.core.Future.lambda$toCompletionStage$2(Future.java:360)
    at io.vertx.core.Future$$Lambda$889/0x0000000000000000.handle(Unknown Source)
    at io.vertx.core.impl.future.FutureImpl$3.onSuccess(FutureImpl.java:124)
    at io.vertx.core.impl.future.FutureBase.emitSuccess(FutureBase.java:62)
    at io.vertx.core.impl.future.FutureImpl.tryComplete(FutureImpl.java:179)
    at io.vertx.core.impl.future.PromiseImpl.tryComplete(PromiseImpl.java:23)
    at io.vertx.sqlclient.impl.QueryResultBuilder.tryComplete(QueryResultBuilder.java:102)
    at io.vertx.sqlclient.impl.QueryResultBuilder.tryComplete(QueryResultBuilder.java:35)
    at io.vertx.core.Promise.complete(Promise.java:66)
    at io.vertx.core.Promise.handle(Promise.java:51)
    at io.vertx.core.Promise.handle(Promise.java:29)
    at io.vertx.core.impl.future.FutureImpl$3.onSuccess(FutureImpl.java:124)
    at io.vertx.core.impl.future.FutureBase.lambda$emitSuccess$0(FutureBase.java:54)
    at io.vertx.core.impl.future.FutureBase$$Lambda$625/0x0000000000000000.run(Unknown Source)
    at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
    at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)
    at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.base/java.lang.Thread.run(Thread.java:836)
Caused by: java.lang.IllegalStateException: Session/EntityManager is closed
    at org.hibernate.internal.AbstractSharedSessionContract.checkOpen(AbstractSharedSessionContract.java:393)
    at org.hibernate.engine.spi.SharedSessionContractImplementor.checkOpen(SharedSessionContractImplementor.java:148)
    at org.hibernate.reactive.session.impl.ReactiveSessionImpl.checkOpen(ReactiveSessionImpl.java:1561)
    at org.hibernate.internal.AbstractSharedSessionContract.checkOpenOrWaitingForAutoClose(AbstractSharedSessionContract.java:399)
    at org.hibernate.internal.SessionImpl.getEntityUsingInterceptor(SessionImpl.java:592)
    at org.hibernate.loader.Loader.getRow(Loader.java:1609)
    at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:747)
    at org.hibernate.loader.Loader.getRowsFromResultSet(Loader.java:1046)
    at org.hibernate.reactive.loader.hql.impl.ReactiveQueryLoader.getRowsFromResultSet(ReactiveQueryLoader.java:223)
    at org.hibernate.reactive.loader.ReactiveLoaderBasedResultSetProcessor.reactiveExtractResults(ReactiveLoaderBasedResultSetProcessor.java:72)
    at org.hibernate.reactive.loader.hql.impl.ReactiveQueryLoader$1.reactiveExtractResults(ReactiveQueryLoader.java:72)
    at org.hibernate.reactive.loader.ReactiveLoader.reactiveProcessResultSet(ReactiveLoader.java:146)
    at org.hibernate.reactive.loader.ReactiveLoader.lambda$doReactiveQueryAndInitializeNonLazyCollections$0(ReactiveLoader.java:77)
    at org.hibernate.reactive.loader.ReactiveLoader$$Lambda$1220/0x0000000000000000.apply(Unknown Source)
    at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1072)
    ... 23 more

java.lang.IllegalStateException: Session/EntityManager is closed

我哪里錯了? 我試圖做的事情根本無效嗎? 如果是這樣,你能推薦一些不同的東西嗎?

謝謝!

如您所見,來自 DB 的響應由名為vert.x-eventloop-thread-8的線程處理。 這是預期的:來自反應式 sql 驅動程序的所有響應都在 eventloop 線程上傳遞。

您看到的IllegalStateException可能是由於 Session 尚未在 eventloop 線程上打開這一事實引起的 - 這會導致諸如當所有這些代碼都設計為在一個單個事件循環(並且不需要多個線程,更不用說使其成為多線程會降低性能)。 您應該能夠通過在調用 Panache Reactive 之前從代碼中記錄線程名稱來驗證這一點:我敢打賭它不是vert.x-eventloop-thread-X

換句話說,Panache Reactive 旨在用於完全響應式的上下文。

從 Quarkus 2.1 開始,Panache Reactive 應該能夠檢測到這種模式並將操作卸載到正確的線程上。

我找到了一種訪問反應式華麗實體的方法,但由於某種原因,它只能工作 3 次,然后其他調用因超時而失敗。

@ServerEndpoint("/websocket/{name}")
@ApplicationScoped
class StartWebSocket(
    val sessionFactory: SessionFactory
) {

    @OnOpen
    fun onOpen(session: Session, @PathParam("name") name: String) {
        sessionFactory.withSession { SimpleUser.findByName(name) }.subscribe().with{ println("Found: ${it?.username}") }
   }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM