Spring Boot Async ResultSet is closed

Moo*_*ker 5 java spring asynchronous spring-boot

I'm implementing notifications using Spring Boot and notifying users in a different thread using @Async.

Without this annotation, everything works well, but for when I put it on the method I use to notify, in only one observable entity, the observers don't get notified and I get this stack trace:

Unexpected exception occurred invoking async method: public void pt.ulisboa.tecnico.socialsoftware.tutor.notifications.NotificationServic
e.notifyObservers(package.notifications.Observable,package.notifications.domain.Notification,ppackage.user.User)                                
                                                                                                                                                                                                                                               
org.hibernate.exception.GenericJDBCException: could not initialize a collection: [package.course.CourseExecution.users#11]
        at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:47) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]
        at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]
        at org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer.initialize(AbstractLoadPlanBasedCollectionInitializer.java:97) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]
        at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:707) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]
        at org.hibernate.event.internal.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:76) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]
        at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:108) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]                                                                          at org.hibernate.internal.SessionImpl.initializeCollection(SessionImpl.java:2145) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]
        at org.hibernate.collection.internal.AbstractPersistentCollection$4.doWork(AbstractPersistentCollection.java:589) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]
        at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:264) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]
        at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:585) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]
        at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:149) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]
        at org.hibernate.collection.internal.PersistentSet.toString(PersistentSet.java:327) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]
        at java.base/java.lang.String.valueOf(String.java:2951) ~[na:na]
        at java.base/java.io.PrintStream.println(PrintStream.java:897) ~[na:na]
        at package.course.CourseExecution.Notify(CourseExecution.java:210) ~[classes/:na]
        at package.notifications.NotificationService.notifyObservers(NotificationService.java:82) ~[classes/:na]
        at package.notifications.NotificationService$$FastClassBySpringCGLIB$$d43e740c.invoke(<generated>) ~[classes/:na]
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:769) ~[spring-aop-5.2.2.RELEASE.jar:5.2.2.RELEASE]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.2.2.RELEASE.jar:5.2.2.RELEASE]                                                                             
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747) ~[spring-aop-5.2.2.RELEASE.jar:5.2.2.RELEASE]                                                                                 
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:366) ~[spring-tx-5.2.2.RELEASE.jar:5.2.2.RELEASE]                                                        
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:99) ~[spring-tx-5.2.2.RELEASE.jar:5.2.2.RELEASE]                                                                              
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.2.RELEASE.jar:5.2.2.RELEASE]                                                                             
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747) ~[spring-aop-5.2.2.RELEASE.jar:5.2.2.RELEASE]                                                                                         at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115) ~[spring-aop-5.2.2.RELEASE.jar:5.2.2.RELEASE]                                               
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]                                                                                                                                                         
        at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]                                                                                                                                                                            
Caused by: org.postgresql.util.PSQLException: This statement has been closed.                                                                                                                                                                  
        at org.postgresql.jdbc.PgStatement.checkClosed(PgStatement.java:705) ~[postgresql-42.2.8.jar:42.2.8]                                                                                                                                   
        at org.postgresql.jdbc.PgPreparedStatement.setInt(PgPreparedStatement.java:270) ~[postgresql-42.2.8.jar:42.2.8]                                                                                                                        
        at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.setInt(HikariProxyPreparedStatement.java) ~[HikariCP-3.4.1.jar:na]                                                                                                              
        at org.hibernate.type.descriptor.sql.IntegerTypeDescriptor$1.doBind(IntegerTypeDescriptor.java:46) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]                                                                                               at org.hibernate.type.descriptor.sql.BasicBinder.bind(BasicBinder.java:73) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]                                                                                                                       at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:276) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]                                                                                                  at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:271) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]                                                                                                  at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.bindPositionalParameters(AbstractLoadPlanBasedLoader.java:320) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]                                                    
        at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.bindParameterValues(AbstractLoadPlanBasedLoader.java:291) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]                                                         
        at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.prepareQueryStatement(AbstractLoadPlanBasedLoader.java:210) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]                                                       
        at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeQueryStatement(AbstractLoadPlanBasedLoader.java:162) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]                                                       
        at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:104) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]                                                                 
        at org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer.initialize(AbstractLoadPlanBasedCollectionInitializer.java:87) ~[hibernate-core-5.4.9.Final.jar:5.4.9.Final]                                        
        ... 25 common frames omitted
Run Code Online (Sandbox Code Playgroud)

Caused by异常消息总是相同的,另外一个,总是不

跟踪中提到的方法是:

  • 通知程序(异步方法)
    @Async("notifyExecutor") // Even with the default executor, the error occurs
    @Retryable(
            value = { SQLException.class },
            backoff = @Backoff(delay = 5000))
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public void notifyObservers(Observable observable, Notification notification, User exclude) {
        observable.Notify(notification, exclude);
    }
Run Code Online (Sandbox Code Playgroud)
  • 调用通知程序的方法(需要的服务是@Autowired
    @Retryable(value = { SQLException.class }, backoff = @Backoff(delay = 5000))
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public AnnouncementDto createAnnouncement(AnnouncementDto announcementDto) {

        checkIfConsistentAnnouncement(announcementDto);

        User user = getTeacher(announcementDto.getUserId());

        CourseExecution courseExecution = getCourseExecution(announcementDto.getCourseExecutionId());

        if (announcementDto.getCreationDate() == null) {
            announcementDto
                    .setCreationDate(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")));
        }

        // Announcement has a CourseExecution as attribute
        Announcement announcement = new Announcement(user, courseExecution, announcementDto);
        entityManager.persist(announcement);

        NotificationDto notification = NotificationsCreation.create(ADD_ANNOUNCEMENT_TITLE,
                List.of(announcement.getUser().getName()), ADD_ANNOUNCEMENT_CONTENT,
                List.of(announcement.getTitle(), user.getName()), Notification.Type.ANNOUNCEMENT);

        this.notify(courseExecution, notification, user);

        return new AnnouncementDto(announcement);
    }

    // Calls the Async method
    private void notify(CourseExecution course, NotificationDto notification, User user) {
        notificationService.notifyObservers(course, notificationService.createNotification(notification), user);
    }
Run Code Online (Sandbox Code Playgroud)
  • 发生错误的方法(仅在访问元素时)
    @Override
    public void Notify(Notification notification, User user) {
        for (Observer observer : this.users) { // Error occurs here, doesn't get inside the loop
            if (((User) observer).getId() == user.getId()) {
                continue;
            }

            observer.update(this, notification);
        }
    }
Run Code Online (Sandbox Code Playgroud)

我已经看到了这些答案,但它们不适用于这里。我认为非常奇怪的是,这只发生Announcement在其他可观察对象上,而不发生在其他可观察对象上。因为我想要所有观察者的公告CourseExecution,所以我做了CourseExecution可观察的,当有新公告时,我们通知所有CourseExecution观察者。

有人能帮助我吗?

use*_*710 4

我想 Spring 跟踪器上的某个人可能能够更详细地回答这个问题。

正如Deadron在他的回答中提到的,这似乎是 TX 管理的问题。@Async使得控制流立即返回到调用函数,这(从事务管理器的角度来看)意味着语句成功执行并且可能发生提交。标记为 Async 的方法不会与调用者具有相同的事务上下文,即使标记为propagation=REQUIRED(只会返回一个新事务)

@Transactional(isolation = Isolation.REPEATABLE_READ)尝试检查现有事务(默认传播级别为REQUIRED,如果存在则支持现有事务,如果不存在则创建新事务)。该事务确实在方法调用时存在,但在方法@Async必须在事务内执行任何工作时(即绑定参数,如堆栈跟踪中所示)不存在。此时,无法访问该原始事务,因为它对于原始线程而言是本地的。

根据这个答案:

在 REPEATABLE READ 下,第二个 SELECT 保证至少显示从第一个 SELECT 返回的未更改的行。并发事务可能会在那一分钟内添加新行,但无法删除或更改现有行。

此外,Isolation Javadocs的以下部分:

/**
 * A constant indicating that dirty reads and non-repeatable reads are
 * prevented; phantom reads can occur. This level prohibits a transaction
 * from reading a row with uncommitted changes in it, and it also prohibits
 * the situation where one transaction reads a row, a second transaction
 * alters the row, and the first transaction rereads the row, getting
 * different values the second time (a "non-repeatable read").
 * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ
 */
REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),
Run Code Online (Sandbox Code Playgroud)

考虑到上面描述的@Transactional@Async交互,有助于解释这种行为,特别是如果您Observable持有对用户的引用,而该引用又对行有某种引用(即为该@Async方法创建了新事务,但原始事务尚未创建)但已完全投入)。您可以尝试以下操作:

  1. 尝试将传播级别设置为MANDATORY。该notifyObservers方法是否成功调用,或者是否收到事务未激活的错误?如果出现错误,则可以确认前一段内容。
  2. 如果确实出现错误,当您将传播级别设置为 时,行为是否会发生变化REQUIRES_NEW
  3. 如果将隔离级别设置为默认级别会发生什么?
  4. 如果您createAnnouncement在返回之前人为地在方法中引入减速,会发生什么情况?例如Thread.sleep(5000),同时不改变任何其他内容?错误仍然存​​在吗?如果不是,那就可以证明上一段中提到的时间问题REPEATABLE_READ