Java 创建引用可以在带有objectify的事务内部抛出ConcurrentModificationException
我在事务内部进行祖先查询,如下所示:Java 创建引用可以在带有objectify的事务内部抛出ConcurrentModificationException,java,google-app-engine,google-cloud-datastore,objectify,Java,Google App Engine,Google Cloud Datastore,Objectify,我在事务内部进行祖先查询,如下所示: Task task = OfyService.ofy().load().type(Task.class) .ancestor(jobKey) .filter("locationKey", locationKey) .first().now(); 稍后在事务中,我创建并保存一个新实体,该实体使用我
Task task = OfyService.ofy().load().type(Task.class)
.ancestor(jobKey)
.filter("locationKey", locationKey)
.first().now();
稍后在事务中,我创建并保存一个新实体,该实体使用我在祖先()
中使用的键作为Ref
属性:
Task newTask=新任务(作业密钥)
//具有以下属性和构造函数的任务POJO:
@母公司
私有Ref作业密钥;
公共任务(密钥jobKey){
this.jobKey=Ref.create(jobKey);
}
当我的整个方法在一秒钟内运行几次时,我在jobKey
上得到一个ConcurrentModificationException
。这很奇怪,因为我所做的就是创建一个引用并将其设置为属性。我看了一下Ref
的描述,它说:
请注意,这些方法可能会也可能不会引发运行时异常
与数据存储操作相关;ConcurrentModificationException,
DatastoreTimeoutException、DatastoreFailureException和
数据存储需要IndexException。某些引用隐藏了以下数据存储操作:
可以抛出这些异常
有人能给我解释一下
Ref
是怎么回事,为什么它会给我带来ConcurrentModificationException
?这似乎是罪魁祸首。这是Objectify弄乱和误用异常系统的API,目的是传递一个重试
事务系统有三种主要方法来解决一个基本问题。想象一下,这一系列命令,都是单个事务的一部分(用SQL编写,假设它可读性强,熟悉程度足以理解。这只是一个示例):
//把斯皮蒂的10块钱转给我
int rBalance=[从用户='rzwitserloot'所在的帐户中选择余额]
int sBalance=[从用户='Speedy'所在的帐户中选择余额]
如果(平衡<10)抛出新平衡不足异常();
sBalance-=10;
rBalance+=10;
[更新帐户设置余额=%r余额%,其中用户='rzwitserroot']
[更新帐户设置余额=%s余额%,其中用户='Speedy']
犯罪
看起来很安全,对吗
不,事实上,这真的很棘手。想象一下,在右边,在<代码> Sale==10;<代码>,你从ATM机上从你的账户中提取50美元(而你的账户一开始就有50美元)
你现在多了50美元,你的账户余额应该是-10,但实际上是40美元
哇哦
太可怕了
解决此问题的方法有三种:
[从帐户中选择余额,其中用户='Speedy']
命令现在返回的结果与先前返回的结果不同,这意味着整个交易现在无效,需要从顶部重新运行。这就解决了问题:整个区块重新运行,意识到您现在的余额为0,并通过抛出不足余额异常
正确中止转移资金的尝试。我们避免了世界锁,代价是一些簿记和对任何提交执行原子“快速检查是否有任何查询涉及到自那时以来发生的任何更改”操作
这正是您在这里遇到的问题——这就是objectify在抛出ConcurrentModificationException时的含义。这是糟糕的API设计:这不是正确的异常,一般来说,您不应该仅仅因为名称听起来似乎模糊匹配就重用现有的异常。但是,无论如何,你必须接受客观化在这方面犯了一个错误的事实
如果你从一开始就没有正确编程,那么一般的解决方案是非常复杂的,而且听起来你好像没有
看,这里有一个巨大的问题:代码不仅仅是db/持久层中的原语。数据库引擎无法重播该块。毕竟,这个块包含了一堆java代码
不,需要告诉代码本身重新开始
这就更加复杂了。计算机是非常可靠的机器。如果两个单独的进程(比如,你向我订购10美元资金转账的银行网络界面和ATM机)发生冲突,并且都被迫从头开始执行命令,运气不好,两台机器可靠地重试,并可靠地相互干扰第二次,再次重试,并将继续相互吻合,总是强迫对方重试,永远卡住
解决办法是掷骰子。不,真的。爸爸需要一双新鞋。解决方案是:如果发生冲突,则随机等待一段时间(但对于发生的每个冲突,从越来越大的潜在暂停中进行选择,直到某件事情成功),从而确保两个系统最终停止
// Task POJO with the following property and constructor:
@Parent
private Ref<Job> jobKey;
public Task(Key<Job> jobKey) {
this.jobKey = Ref.create(jobKey);
}
// transfer 10 bucks from speedy to me
int rBalance = [SELECT balance FROM accounts WHERE user = 'rzwitserloot']
int sBalance = [SELECT balance FROM accounts WHERE user = 'Speedy']
if (sBalance < 10) throw new BalanceInsufficientException();
sBalance -= 10;
rBalance += 10;
[UPDATE accounts SET balance = %rBalance% WHERE user = 'rzwitserloot']
[UPDATE accounts SET balance = %sBalance% WHERE user = 'Speedy']
COMMIT;