我想使用Groovy闭包来处理来自SQL表的数据。对于每个新行,计算将取决于先前计算的内容。但是,新的行可能会在应用程序的进一步运行时变得可用,所以我希望能够重新加载闭包,初始化它是在上次运行应用程序时最后一次执行闭包时所具有的中间状态。
例如,打算计算超过3行的移动平均值的闭包将实现如下:
def prev2Val = null
def prevVal = null
def prevId = null
Closure c = { row ->
println([ prev2Val, prevVal, prevId])
def latestVal = row['val']
if (prev2Val != null) {
def movMean = (prev2Val + prevVal + latestVal) / 3
sql.execute("INSERT INTO output(id, val) VALUES (?, ?)", [prevId, movMean])
}
sql.execute("UPDATE test_data SET processed=TRUE WHERE id=?", [row['id']])
prev2Val = prevVal
prevVal = latestVal
prevId = row['id']
}
test_data
有3列:id
(自动递增的主键),value
和processed
。移动平均值基于前两个值计算,并插入output
表中,与前一行的id
相对应。已处理的行标有processed=TRUE
。
如果所有数据从一开始就可用,则可以这样调用:
sql.eachRow("SELECT id, val FROM test_data WHERE processed=FALSE ORDER BY id", c)
问题出现在应用程序运行后新行可用时。这可以通过每次处理一个小批量来模拟(例如,在前一个语句中使用LIMIT 5
)。
我希望能够在eachRow
执行结束时转储闭包的完整状态(例如,在数据库中的某处保存中间数据)并在我重新初始化时重新初始化它 - 运行整个应用程序(通过从数据库加载这些中间变量)。
在这个特定示例中,我可以通过存储prev2Val
,prevVal
和prevId
的值来手动执行此操作,但我正在寻找一种通用解决方案,其中确切知道哪些变量没有必要使用。
类似c.getState()
之类的东西会返回[ prev2Val: 1, prevVal: 2, prevId: 6]
(例如),下次执行应用程序时我可以使用c.setState([ prev2Val: 1, prevVal: 2, prevId: 6]
)(如果有的话)状态存储)。
我还需要从列表中排除sql
。似乎可以使用c.@sql=null
完成此操作。
我意识到这不太可能在一般情况下起作用,但我正在寻找对大多数情况来说足够通用的东西。我已尝试dehydrate
,序列化和rehydrate
关闭,如this Groovy issue中所述,但我不确定如何保存和存储所有@
字段一次操作。
这可能吗?是否有更好的方法来记住执行之间的状态,假设闭包使用的变量列表不一定事先知道?
答案 0 :(得分:2)
从长远来看,不确定这是否会起作用,你可能最好返回一个包含值的列表,以传递给闭包以获取下一组数据,但是你可以查询闭包的绑定。
假设:
def closure = { row ->
a = 1
b = 2
c = 4
}
如果你执行它:
closure( 1 )
然后你可以编写一个像:
这样的函数def extractVarsFromClosure( Closure cl ) {
cl.binding.variables.findAll {
!it.key.startsWith( '_' ) && it.key != 'args'
}
}
执行时:
println extractVarsFromClosure( closure )
打印:
['a':1, 'b':2, 'c':4]
但是,本地绑定中定义的任何“免费”变量(没有def
)也会在闭包中绑定,所以:
fish = 42
println extractVarsFromClosure( closure )
将打印:
['a':1, 'b':2, 'c':4, 'fish':42]
但是
def fish = 42
println extractVarsFromClosure( closure )
不会打印值fish