【问题标题】:Hibernate sessions fetch stale data when using async controllers and long pollingHibernate 会话在使用异步控制器和长轮询时获取陈旧数据
【发布时间】:2015-09-15 19:07:38
【问题描述】:

我对 Grails 中的异步控制器有疑问。考虑以下控制器:

@Transactional(readOnly=true)
class RentController  {
    def myService


    UserProperties props
    def beforeInterceptor = {
        this.props = fetchUserProps() 
    }


    //..other actions

    @Transactional
    def rent(Long id) {
        //check some preconditions here, calling various service methods...
        if (!allOk) {
            render status: 403, text: 'appropriate.message.key'
            return
        }

        //now we long poll because most of the time the result will be
        //success within a couple of seconds
        AsyncContext ctx = startAsync()
        ctx.timeout = 5 * 1000 * 60 + 5000
        ctx.start {
            try {
                //wait for external service to confirm - can take a long time or even time out
                //save appropriate domain objects if successful
                //placeRental is also marked with @Transactional (if that makes any difference)
                def result = myService.placeRental() 
                if (result.success) {
                    render text:"OK", status: 200
                } else {
                    render status:400, text: "rejection.reason.${result.rejectionCode}"
                }
            } catch (Throwable t) {
                log.error "Rental process failed", t
                render text: "Rental process failed with exception ${t?.message}", status: 500
            } finally {
                ctx.complete()
            }
        }
    }
}

控制器和服务代码似乎工作正常(尽管上面的代码被简化了),但有时会导致数据库会话“卡在过去”。

假设我有一个UserProperties 实例,它的属性accountId 在应用程序的其他位置从1 更新为20,而rent 操作正在异步块中等待。由于异步块最终以一种或另一种方式终止(它可能成功、失败或超时),应用程序有时会得到一个陈旧的UserProperties 实例和accountId: 1。假设我刷新了更新后的用户属性页面,每 10 次刷新我会看到 accountId: 1 大约 1 次,而其余时间它将是 20 - 这是在我的开发机器上,没有其他人正在访问应用程序(尽管在生产中可以观察到相同的行为)。我的连接池也有 10 个连接,所以我怀疑这里可能存在关联。

还会发生其他奇怪的事情 - 例如,我会从像 render (UserProperties.list() as JSON) 这样简单的动作中得到 StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) - 在响应已经呈现之后(除了日志中的噪音之外,成功)并且尽管动作是注释为@Transactional(readOnly=true)

似乎不是每次都出现过时的会话,到目前为止,我们的解决方案是每天晚上重新启动服务器(该应用目前用户很少),但该错误很烦人,而且原因很难查明。我的猜测是,由于异步代码,DB 事务不会被提交或回滚,但 GORM、Spring 和 Hibernate 有很多问题可能会卡住。

我们使用 Postgres 9.4.1(开发机器上的 9.2,同样的问题)、Grails 2.5.0、Hibernate 插件 4.3.8.1、Tomcat 8、缓存插件 1.1.8、Hibernate Filter 插件 0.3.2 和审计日志插件 1.0.1(显然还有其他东西,但这感觉可能是相关的)。我的数据源配置包含:

hibernate {
    cache.use_second_level_cache = true
    cache.use_query_cache = false
    cache.region.factory_class = 'org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory'
    singleSession = true
    flush.mode = 'manual'
    format_sql = true
}

【问题讨论】:

  • 为什么在控制器级别使用@Transactional?
  • 因为大多数其他操作都是只读的,而且我在自动生成的 Grails 控制器中看到了这种模式:让 Hibernate 知道一个操作是只读的,让它跳过一些原本需要的步骤.你也会得到一个例外,试图写任何很好的反馈,让你知道你的假设是错误的。操作上的@Transactional 会覆盖该操作的只读设置。
  • 我已经从控制器中删除了@Transactional(并且从有问题的操作中,服务方法被注释了,无论如何),没有任何改进。

标签: spring hibernate postgresql grails asynchronous


【解决方案1】:

Grails bug. 令人讨厌的是,一切看起来都还不错,直到您的应用开始在应用中完全不相关的部分表现得滑稽。

【讨论】:

    猜你喜欢
    • 2012-02-24
    • 1970-01-01
    • 1970-01-01
    • 2015-01-17
    • 1970-01-01
    • 2013-03-20
    • 1970-01-01
    • 2020-07-07
    • 1970-01-01
    相关资源
    最近更新 更多