当我在 Jaxrs 资源上添加 @Transactional 时发生异常

Posted

技术标签:

【中文标题】当我在 Jaxrs 资源上添加 @Transactional 时发生异常【英文标题】:Exception occurs when I added @Transactional on Jaxrs Resource 【发布时间】:2019-09-10 08:08:20 【问题描述】:

在 Quarkus 资源中,我尝试在响应中编写 Java 8 流。

我为 Stream 类型创建了 MessageBodyWriter。

@Provider
public class StreamBodyWriter implements MessageBodyWriter<Stream> 

    @Context
    private Providers providers;

    @Override
    public boolean isWriteable(Class<?> type, Type genericType,
                               Annotation[] annotations, MediaType mediaType) 
        return Stream.class.isAssignableFrom(type);
    

    @Override
    public long getSize(Stream stream, Class<?> type, Type genericType,
                        Annotation[] annotations, MediaType mediaType)  return -1; 

    @Override
    public void writeTo(Stream stream, Class<?> type, Type genericType,
                        Annotation[] annotations, MediaType mediaType,
                        MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
            throws IOException, WebApplicationException 

        Object obj = stream.collect(Collectors.toList());
        Class<?> objType = obj.getClass();

        MessageBodyWriter writer = providers.getMessageBodyWriter(objType,
                null, annotations, mediaType);

        writer.writeTo(obj, objType, null, annotations,
                mediaType, httpHeaders, entityStream);

    

Stream 需要一个 jdbc 游标才能使其工作,并且它需要事务支持。

我确定我已经安装了一个 postgres jdbc 扩展,并且应用程序启动时没有错误。

@Transactional
    public Response getAllPosts(
            @QueryParam("q") String q,
            @QueryParam("offset") @DefaultValue("0") int offset,
            @QueryParam("size") @DefaultValue("10") int size

    ) 
        return ok(this.posts.findByKeyword(q, offset, size)).build();
    
@ApplicationScoped
public class PostRepository implements PanacheRepositoryBase<Post, String> 

    public Stream<Post> findByKeyword(String q, int offset, int size) 
        if (q == null || q.trim().isEmpty()) 
            return this.streamAll(Sort.descending("createdAt"))
                    .skip(offset)
                    .limit(size);
         else 
            return this.streamAll(Sort.descending("createdAt"))
                    .filter(p -> p.title.contains(q) || p.content.contains(q))
                    .skip(offset)
                    .limit(size);
        
    
@Entity
public class Post implements Serializable 

    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    String id;
    String title;
    String content;
    LocalDateTime createdAt;

当我在资源上添加 CDI @Transactional 时,在启动阶段收到以下错误。

[38;5;203m: org.jboss.resteasy.spi.UnhandledException: org.hibernate.exception.GenericJDBCException: could not advance using next()
        at org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler.java:381)
        at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:209)
        at org.jboss.resteasy.core.SynchronousDispatcher.writeResponse(SynchronousDispatcher.java:587)
        at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:508)
        at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:252)
        at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:153)
        at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:363)
        at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:156)
        at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:238)
        at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:249)
        at io.quarkus.resteasy.runtime.ResteasyFilter$ResteasyResponseWrapper.sendError(ResteasyFilter.java:64)
        at io.undertow.servlet.handlers.DefaultServlet.doGet(DefaultServlet.java:175)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:686)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:791)
        at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
        at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)
        at io.quarkus.resteasy.runtime.ResteasyFilter.doFilter(ResteasyFilter.java:28)
        at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
        at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
        at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
        at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
        at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
        at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
        at io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68)
        at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)
        at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
        at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
        at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
        at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
        at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
        at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
        at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
        at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
        at io.undertow.server.handlers.PathHandler.handleRequest(PathHandler.java:91)
        at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
        at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:269)
        at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:78)
        at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:133)
        at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:130)
        at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
        at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
        at io.quarkus.undertow.runtime.UndertowDeploymentRecorder$9$1$1.call(UndertowDeploymentRecorder.java:513)
        at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:249)
        at io.undertow.servlet.handlers.ServletInitialHandler.handleRequest(ServletInitialHandler.java:174)
        at io.undertow.server.handlers.HttpContinueReadHandler.handleRequest(HttpContinueReadHandler.java:65)
        at io.quarkus.undertow.runtime.UndertowDeploymentRecorder$1.handleRequest(UndertowDeploymentRecorder.java:92)
        at io.undertow.server.handlers.CanonicalPathHandler.handleRequest(CanonicalPathHandler.java:49)
        at io.quarkus.undertow.deployment.devmode.UndertowHotReplacementSetup.handleHotDeploymentRequest(UndertowHotReplacementSetup.java:85)
        at io.quarkus.undertow.deployment.devmode.UndertowHotReplacementSetup$1$1.handleRequest(UndertowHotReplacementSetup.java:61)
        at io.undertow.server.Connectors.executeRootHandler(Connectors.java:376)
        at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)
        at io.quarkus.runtime.CleanableExecutor$CleaningRunnable.run(CleanableExecutor.java:224)
        at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
        at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
        at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2011)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1535)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1395)
        at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
        at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
        at java.base/java.lang.Thread.run(Thread.java:834)
        at org.jboss.threads.JBossThread.run(JBossThread.java:479)
Caused by: org.hibernate.exception.GenericJDBCException: could not advance using next()
        at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:47)
        at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)
        at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:99)
        at org.hibernate.internal.ScrollableResultsImpl.convert(ScrollableResultsImpl.java:70)
        at org.hibernate.internal.ScrollableResultsImpl.next(ScrollableResultsImpl.java:105)
        at org.hibernate.query.internal.ScrollableResultsIterator.hasNext(ScrollableResultsIterator.java:33)
        at java.base/java.util.Spliterators$IteratorSpliterator.tryAdvance(Spliterators.java:1811)
        at java.base/java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:127)
        at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:502)
        at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:488)
        at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
        at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
        at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
        at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
        at com.example.StreamBodyWriter.writeTo(StreamBodyWriter.java:39)
        at com.example.StreamBodyWriter.writeTo(StreamBodyWriter.java:17)
        at org.jboss.resteasy.core.interception.jaxrs.AbstractWriterInterceptorContext.writeTo(AbstractWriterInterceptorContext.java:193)
        at org.jboss.resteasy.core.interception.jaxrs.ServerWriterInterceptorContext.writeTo(ServerWriterInterceptorContext.java:64)
        at org.jboss.resteasy.core.interception.jaxrs.AbstractWriterInterceptorContext.proceed(AbstractWriterInterceptorContext.java:155)
        at org.jboss.resteasy.core.ServerResponseWriter.lambda$writeNomapResponse$2(ServerResponseWriter.java:156)
        at org.jboss.resteasy.core.interception.jaxrs.ContainerResponseContextImpl.filter(ContainerResponseContextImpl.java:405)
        at org.jboss.resteasy.core.ServerResponseWriter.executeFilters(ServerResponseWriter.java:232)
        at org.jboss.resteasy.core.ServerResponseWriter.writeNomapResponse(ServerResponseWriter.java:97)
        at org.jboss.resteasy.core.ServerResponseWriter.writeNomapResponse(ServerResponseWriter.java:70)
        at org.jboss.resteasy.core.SynchronousDispatcher.writeResponse(SynchronousDispatcher.java:578)
        ... 59 more
Caused by: java.sql.SQLException: ResultSet is closed
        at io.agroal.pool.wrapper.ResultSetWrapper$1.invoke(ResultSetWrapper.java:49)
        at com.sun.proxy.$Proxy81.next(Unknown Source)
        at io.agroal.pool.wrapper.ResultSetWrapper.next(ResultSetWrapper.java:85)
        at org.hibernate.internal.ScrollableResultsImpl.next(ScrollableResultsImpl.java:100)
        ... 79 more

【问题讨论】:

您已经按照文档中的描述设置了属性? quarkus.io/guides/datasource-guide @SimonMartinelli 我刚刚清理了我的代码,然后再次运行应用程序,得到了 updated 异常。奇怪的是,如果我删除所有@Transactional,返回结果,但我不确定是不是基于游标来获取结果。从打印日志中查询所有结果。 看看这里***.com/questions/56430329/… 【参考方案1】:

当您使用返回 Stream 的实体方法时,您需要让该方法在事务中处理它。

在您的代码中,情况并非如此,因为 Stream 由您的 StreamBodyWriter 处理,collect() 方法所在的位置。

要解决此问题,您必须在 getAllPostsfindByKeyword 方法内处理您的流。所以摆脱StreamBodyWriter 并从你的休息端点返回一个集合。

【讨论】:

我知道这种方式行得通,但是如何让Stream在StreamBodyWriter中运行良好? 你不能,它必须是事务的一部分,Quarkus 上的事务实现基于 CDI 拦截器,它将拦截带有 @Transactional 注释的方法调用。我不认为您的 StreamBodyWriter 可能是事务的一部分,因为它将在您的 JAX-RS 资源(定义事务的资源)之外由 resteasy 使用。

以上是关于当我在 Jaxrs 资源上添加 @Transactional 时发生异常的主要内容,如果未能解决你的问题,请参考以下文章

使用 Guice 3.0 + JaxRS 2.0 对 REST API 进行版本控制

当我在 Visual Studio 2017 的解决方案资源管理器中右键单击 Web 项目时,无法将添加->现有项目视为 Azure Web 作业选项

将 JaxRS REST 服务与 WebSocket 集成

如何在 Transact SQL 中添加数字?

Qt 资源文件利用率

Transact-SQL 查询小计列