【发布时间】: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