java - 当向嵌入式 tomcat 添加 war 时,当前线程没有 session

标签 java hibernate tomcat spring-boot grails3

在我们的项目中,我们遇到过,当将另一个现有的 war 文件添加到 Grails/Spring boot 中的嵌入式 tomcat 容器时,我们的默认 Hibernate session 似乎丢失了。

一旦我们对语句进行注释,添加 war 文件,一切都会按预期进行。当我们将应用程序部署到生产环境时,这个问题就不存在了,无论如何我们在开发时尝试添加的 war 文件也会在生产环境中启动。

我们正在使用 Grails 3.3.1,它建立在 Spring 引导之上,并通过 Applications.groovy 文件以这种方式将现有的 war 文件添加到嵌入的 tomcat。

class Application extends GrailsAutoConfiguration implements EnvironmentAware {
static void main(String[] args) {
    GrailsApp.run(Application, args)
}

@Bean
EmbeddedServletContainerFactory servletContainerFactory() {
     return new TomcatEmbeddedServletContainerFactory() {

        @Override
        protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(Tomcat tomcat) {
              try {
                    // Ensure that the folder exists
                    tomcat.getHost().getAppBaseFile().mkdir()
                    String projektrod = System.getProperty('user.dir')
                    // Add external Orbeon war file
                    Context context = tomcat.addWebapp("/blanketdesigner/orbeon", "${projektrod}\\..\\blanket-orbeon-plugin\\orbeon\\orbeon.war")
                    context.setParentClassLoader(getClass().getClassLoader())
              } catch (ServletException ex) {
                    throw new IllegalStateException("Failed to add webapp", ex)
              }
              return super.getTomcatEmbeddedServletContainer(tomcat)
        }
    }
}

当上述设置完成后,应用程序和外部 war 文件都会启动,并按预期工作,但我们必须在域对象上调用 .withTransaction、.withNewTransaction、.withNewSession 或类似的东西。

这也发生在用@Transactional 注释的服务中。如果我们不这样做,我们会遇到以下异常:

No Session found for current thread. Stacktrace follows:
java.lang.reflect.InvocationTargetException: null
    at org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:211)
    at org.grails.core.DefaultGrailsControllerClass.invoke(DefaultGrailsControllerClass.java:188)
    at org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:90)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
    at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55)
    at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:77)
    at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:67)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: org.hibernate.HibernateException: No Session found for current thread
    at org.grails.orm.hibernate.GrailsSessionContext.currentSession(GrailsSessionContext.java:117)
    at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:688)
    at org.grails.orm.hibernate.HibernateSession.createQuery(HibernateSession.java:177)
    at org.grails.orm.hibernate.HibernateSession.createQuery(HibernateSession.java:170)
    at org.grails.datastore.gorm.finders.FindAllByFinder.buildQuery(FindAllByFinder.java:63)

如果我们删除添加 war 文件的行

 tomcat.addWebapp("....")

一切都按预期工作,我们可以使用域对象而无需调用 .with...

前面说了,这个问题好像是内嵌的tomcat服务器的问题,还有添加war文件的功能,在旁边。

我们不想在整个代码中调用 .with...,因为这不是正确的解决方案。

为什么我们必须在开发时将 war 文件添加到嵌入式 tomcat 中,因为我们的主要应用程序依赖于其他 war 才能运行所有功能。在生产服务器上,它也作为一个单独的网络应用程序运行。

我们可以接受在开发环境中附加默认 hibernate session 的修复程序,但我不知道如何执行此操作。

想到这样的事情

if(grails.util.Environment.current == grails.util.Environment.DEVELOPMENT) {
    // Then bind new default session to current thread 
}

它应该很容易重现。

将不胜感激任何帮助或建议

谢谢

最佳答案

@WesleyDeKeirsmaeker,通过进一步调试,我发现当我通过调用“.addWebapp(...)”添加外部war文件时,整个Spring Boot环境启动了两次。

这意味着以下设置了两次:

public GrailsSessionContext(SessionFactoryImplementor sessionFactory) {
    this.sessionFactory = sessionFactory;
}

当 Grails 调用 getCurrentSession() 时,上面的内容似乎导致了问题,因为它是由另一个线程设置的。

在处理域对象时,TransactionSynchronizationManager.getResource(sessionFactory);返回空值。

public Session currentSession() throws HibernateException {
    Object value = TransactionSynchronizationManager.getResource(sessionFactory);
    if (value instanceof Session) {
        return (Session) value;
    }

    if (value instanceof SessionHolder) {
        SessionHolder sessionHolder = (SessionHolder) value;
        Session session = sessionHolder.getSession();
        if (TransactionSynchronizationManager.isSynchronizationActive() && !sessionHolder.isSynchronizedWithTransaction()) {
            TransactionSynchronizationManager.registerSynchronization(createSpringSessionSynchronization(sessionHolder));
            sessionHolder.setSynchronizedWithTransaction(true);
            // Switch to FlushMode.AUTO, as we have to assume a thread-bound Session
            // with FlushMode.MANUAL, which needs to allow flushing within the transaction.
            FlushMode flushMode = HibernateVersionSupport.getFlushMode(session);
            if (flushMode.equals(FlushMode.MANUAL) && !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
                HibernateVersionSupport.setFlushMode(session, FlushMode.AUTO);
                sessionHolder.setPreviousFlushMode(flushMode);
            }
        }
        return session;
    }

    if (jtaSessionContext != null) {
        Session session = jtaSessionContext.currentSession();
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            TransactionSynchronizationManager.registerSynchronization(createSpringFlushSynchronization(session));
        }
        return session;
    }

    if (allowCreate) {
        // be consistent with older HibernateTemplate behavior
        return createSession(value);
    }

    throw new HibernateException("No Session found for current thread");
} 

我在想是否有办法将 args 传递给启动我的外部 war 文件的子容器。如果我能够传递参数,我可能能够在启动时禁用与数据库相关的 beans/auto 配置,并且 GrailsS​​essionContext 将不会被调用两次。

在这里找到参数 Disable related auto configurations .

有没有办法将参数传递给子容器?我正在寻找一种 Hook 方法或其他方法。

关于java - 当向嵌入式 tomcat 添加 war 时,当前线程没有 session ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47227666/

相关文章:

java - Windows Server 2012 上的 [SYN] 上的 TCP 套接字 accept() 返回

java - SOAP href XML 映射问题 - 哪个映射有帮助?

java - 删除父实体而不删除子实体 (jpa)

java - 在 Java 中建立的 HTTP 连接因重定向过多而失败?

tomcat - Jboss-如何在不同端口运行多个web应用

java - 是否有必要在 pom 中指定 maven 插件的版本?

java - Eclipse - 强制刷新 ID

java - Jetty 对 http 请求中的绝对 URL 请求返​​回 400

java - 如果我们使用@OneToMany注释,我们是否必须在数据库中本地定义一对多关系

java - Spring + hibernate : a different object with the same identifier value was already associated with the session