在我们的项目中,我们体验到,当在Grails / Spring启动时向嵌入式tomcat容器中添加另一个现有的war文件时,我们的默认Hibernate会话似乎丢失了。
一旦我们评论该声明,添加war文件,一切都按预期工作。当我们将应用程序部署到生产环境时,这个问题不存在,无论如何我们尝试在开发时添加的war文件也会在生产时启动。
我们正在使用Grails 3.3.1,它是在Spring启动时构建的,并通过Applications.groovy文件以这种方式将现有war文件添加到emnbedded 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来使所有函数都能正常工作。在生产服务器上,它也作为单独的Web应用程序运行。
我们可以使用修复程序在环境开发时附加默认的hibernate会话,但我无法弄清楚如何执行此操作。
想到这样的事情
if(grails.util.Environment.current == grails.util.Environment.DEVELOPMENT) {
// Then bind new default session to current thread
}
应该很容易重现。
欢迎任何帮助或建议
由于
答案 0 :(得分:1)
@WesleyDeKeirsmaeker,通过进一步调试,我发现当我通过调用“.addWebapp(...)”添加外部war文件时,整个Spring Boot环境启动了两次。
这意味着以下两次设置:
public GrailsSessionContext(SessionFactoryImplementor sessionFactory) {
this.sessionFactory = sessionFactory;
}
当Grails调用getCurrentSession()时,上面似乎导致了问题,因为它是由另一个线程设置的。
使用域对象时,TransactionSynchronizationManager.getResource(sessionFactory);返回null。
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文件的子容器。如果我能够传递参数,可能我将能够在启动时禁用与数据库相关的bean / auto配置,并且GrailsSessionContext将不会被调用两次。
在Disable related auto configurations找到参数。
有没有办法将参数传递给子容器?我正在寻找一种方法来挂钩或者什么。