更新:
经过@meltingsnower的提示,发现service的get方法没有@Transactional(readOnly = true),果然加上就好了,没加的时候get之后就有errors了,事物提交导致了所有dirty的全update了,但是因为有个errors,所以validate失败了。
这个问题太绕疼了,根本上还是该@Transactional(readOnly = true)的地方忘记加导致的。
====================================
今天遇到个Grails的非常奇怪的问题,最初的现象是entity.save失败,然后没有任何的errors
仔细检查发现save调用,没有发update sql,怀疑是dirty check没有发现变化;但是save前后对象属性对比,发现属性确实是变化了,为什么dirty check失败了呢?
介绍一下该对象名为Ticket,有一个关联对象(多对一)Warehouse
大致结构为:
class Ticket { Warehouse warehouse String deviceName String serialNumber }
在controller里面先根据id get该对象,然后连续根据参数进行数据绑定,连续几次使用binddata,然后出现问题了。
经过仔细排查,发现下面这段代码
A:
if (StringUtils.isBlank(params.warehouseId)) {
entity.warehouse = null
entity.warehouseName = null
entity.shippingAgent = null
} else {
Long wId = params.warehouseId.toLong()
Warehouse w = warehouseService.get(wId)
if (wId != entity.warehouse?.id) {
entity.warehouse = w
entity.warehouseName = w.name
entity.shippingAgent = w.shippingAgent
}
}
必须改为:
B:
if (StringUtils.isBlank(params.warehouseId)) {
entity.warehouse = null
entity.warehouseName = null
entity.shippingAgent = null
} else {
Long wId = params.warehouseId.toLong()
if (wId != entity.warehouse?.id) {
Warehouse w = warehouseService.get(wId)
entity.warehouse = w
entity.warehouseName = w.name
entity.shippingAgent = w.shippingAgent
}
}
才好用,也就是说不能调用Warehouse w = warehouseService.get(wId)
这段代码A当中如果request提交的warehouseId没有发生改变,那么就不会发update,如果改变了,就会发update;改为B,则正常发update。
所以独立写了一个函数来专门测试这个问题是否可以重现以确定是否是调用Warehouse w = warehouseService.get(wId)的问题,结果you guess what,独立函数里面,无论是否调用Warehouse w = warehouseService.get(wId),都可以正确发出update sql。
看来warehouseService.get(wId)也是受害者,只好进一步进行排除。
最后发现因为画面有个字段变化,导致第二次bindData的时候,一个boolean类型(不是Boolean)在request的时候传递了个undefined上来,即该字段绑定失败。
在binddata的include里面去掉该字段,发现全好用了;但是bindData没有报任何错误,entity.errors也一直是没有错误。
Grails的文档里面http://grails.org/doc/latest/ref/Controllers/bindData.html 明确说明:
The underlying implementation uses Spring's Data Binding framework. If the target is a domain class, type conversion errors are stored in the errors property of the domain class.
但是实际上并没有如此。
所以,现在的问题是:
如果第二次binddata错误,同时又调用了调用Warehouse w = warehouseService.get(wId),那么不发送update sql,即dirty check失败了。
如果第二次binddata错误,但是不掉用Warehouse w = warehouseService.get(wId),那么发送update sql
如果第二次binddata正确,那么无论是否调用Warehouse w = warehouseService.get(wId),都发送update sql。
but why...??? 现在正在进一步分析中,我觉得这应该是gorm的一个bug。