Spring Boot 在关闭时关闭休眠会话 - 在 @Async 方法完成之前
Posted
技术标签:
【中文标题】Spring Boot 在关闭时关闭休眠会话 - 在 @Async 方法完成之前【英文标题】:Spring Boot closes hibernate session on shutdown - before @Async methods are finished 【发布时间】:2018-02-13 11:41:10 【问题描述】:我遇到了一个 Spring Boot 应用程序的问题,该应用程序在 @Async
任务(使用 EntityManager)完成之前关闭了 EntityManager/session。
有 2 个类与此问题相关:
调度器
预定方法保留有限数量的作业,并在 XYJobProcessor
上调用 @Async
方法来执行实际工作。
@Component
public class XYJobProcessingTimer
private final XYJobService xyJobService;
private final XYJobProcessor xyJobProcessor;
//constructor skipped
@Scheduled(initialDelayString = "$initial_delay", fixedDelayString = "$delay")
public void performXYJobProcessing()
final String ticket = UUID.randomUUID().toString();
final int reservedJobs = xyJobService.findAndReserveReadyXYJobs(ticket);
if (reservedJobs > 0)
final Collection<XYJob> xyJobs = xyJobService.readReservedJobs(ticket);
xyJobProcessor.process(xyJobs);
异步处理器
@Async 注解的方法调用访问 EntityManager 的服务。
@Service
public class XYJobProcessor
private final XYJobService xyJobService;
// constructor skipped
@Async("xyJobProcessorExecutor")
public void process(final Collection<XYJob> jobs)
// This service uses the EntityManager and takes some time depending o job count.
xyJobService.createXYJobsAndDelete(jobs);
配置
运行@Async
任务的Executor
的配置。创建的线程是非守护进程。
@Configuration
public class AsyncExecutorConfiguration
@Bean(name = "xyJobProcessorExecutor")
public Executor xyJobProcessorExecutor()
final SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
executor.setConcurrencyLimit(10);
executor.setThreadNamePrefix("Hasselhoff-");
return executor;
问题
当我关闭应用程序时,spring 会立即关闭 EntityManager 会话 - 在所有 @Async
任务完成之前。这会导致以下异常:
2017-08-31 16:10:54.212 ERROR 12663 --- [Hasselhoff-12] .a.i.SimpleAsyncUncaughtExceptionHandler : Unexpected error occurred invoking async method 'public void de.xy.services.XYJobProcessor.process(java.util.Collection)'.org.springframework.orm.jpa.JpaSystemException: Session is closed!; nested exception is org.hibernate.SessionException: Session is closed!
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:333)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:244)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:521)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:761)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:504)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:292)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673)
at de.xy.services.XYJobService$$EnhancerBySpringCGLIB$$6b9cb1ae.createXYJobsAndDelete(<generated>)
at de.xy.services.XYJobProcessor.process(XYJobProcessor.java:24)
at de.xy.services.XYJobProcessor$$FastClassBySpringCGLIB$$ccc40c8f.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:738)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.aop.interceptor.AsyncExecutionInterceptor$1.call(AsyncExecutionInterceptor.java:115)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at org.springframework.core.task.SimpleAsyncTaskExecutor$ConcurrencyThrottlingRunnable.run(SimpleAsyncTaskExecutor.java:268)
at java.lang.Thread.run(Thread.java:748)
Caused by: org.hibernate.SessionException: Session is closed!
at org.hibernate.internal.AbstractSessionImpl.errorIfClosed(AbstractSessionImpl.java:132)
at org.hibernate.internal.SessionImpl.getPersistenceContext(SessionImpl.java:2088)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:340)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1282)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:465)
at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2963)
at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2339)
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:485)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:147)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231)
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:65)
at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:61)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517)
... 17 common frames omitted
有没有办法让 Spring 在关闭 EntityManager 之前等待 @Async
执行完成?或者这是一个错误?
【问题讨论】:
也许这个线程可以帮助:how-to-shutdown-a-spring-boot-application-in-a-correct-way 感谢您的提示,但我已经优雅地关闭了应用程序。问题是 spring 在 @Async 方法完成工作之前(仍在运行)关闭了 EntityManager,导致异常。 使用不同的执行器,例如ThreadPoolTaskExecutor
,并将waitForTasksToCompleteOnShutdown
属性设置为true
。
这个帖子有帮助吗? github.com/spring-projects/spring-boot/issues/4657
@M.Deinum 我已经尝试过 ThreadPoolTaskExecutor,但是当 waitForTasksToCompleteOnShutdown 设置为 true 时,它的行为与 SimpleAsyncTaskExecutor 相同。但是:由于您的评论,我重新检查了 ThreadPoolTaskExecutor 上的其他可用设置,并找到了我的问题的确切解决方案。感谢您为我指明正确的方向。 (我会添加一个答案)。
【参考方案1】:
感谢@M.Deinum,我发现了如何避免这个问题:
使用ThreadPoolTaskExecutor
(而不是SimpleAsyncTaskExecutor
)并将其设置为awaitTerminationSeconds
属性并结合waitForTasksToCompleteOnShutdown
设置为true
可以解决我的问题。
来自setAwaitTerminationSeconds
的JavaDoc:
设置此执行程序应该执行的最大秒数 关闭时阻塞以等待剩余任务完成 它们在容器的其余部分继续关闭之前执行 下。如果您的剩余任务很可能,这将特别有用 需要访问也由 容器。
这是我的问题的确切描述和解决方案。
【讨论】:
以上是关于Spring Boot 在关闭时关闭休眠会话 - 在 @Async 方法完成之前的主要内容,如果未能解决你的问题,请参考以下文章
如何确保 SFTP 会话始终在 spring-batch 结束时关闭