はじめに
DynamoDBで特定の項目のNumber要素をインクリメント(デクリメント)したくなった。Get→インクリメント→Updateはナンセンスなので、もっと簡単にやる方法を探した。
対処
@DynamoDbAtomicCounterアノテーションを利用すると解決した。
@DynamoDbBean
public class Item {
private String id;
private int type;
private Long value;
@DynamoDbPartitionKey
@DynamoDbAttribute(value = "ITEM_ID")
public String getId() {
return id;
}
@DynamoDbSortKey
@DynamoDbAttribute(value = "TYPE")
public int getType() {
return type;
}
@DynamoDbAtomicCounter(startValue = 100, delta = 50)
@DynamoDbAttribute(value = "VALUE")
public Long getValue() {
return value;
}
}
これを利用する時の注意点としては、DynamoDbAtomicCounterを利用したい変数はLong型で宣言する必要がある。(おそらく存在しない=nullを取る必要があるので、オブジェクト型なのだと推測する。)
あとは、DynamoDbEnhancedClientから作成したDynamoDbTableに対してupdate要求を行う。
Item i = new Item();
i.setId("id");
i.setType(1);
Item result = table.updateItem(item);
これを呼び出すと、idとtypeに合致する項目が存在しなかった場合、startValueで指定した値でupdateされ、resultにはvalue=100のitemが取得される。
逆にidとtypeに合致する項目が存在している場合は、現在のvalueに対してdeltaを加算した値でupdateされ、resultにはvalue=現在の値+deltaのitemが取得される。
注意
この方法を取ると、任意の値でvalueをupdateすることができない。さらに任意のdeltaでインクリメント(デクリメント)することもできなくなる。
ItemクラスのsetValue()を呼び出して、任意の値を設定した上でupdateを呼び出すとDynamoDbExceptionが発生する。
Invalid UpdateExpression: Two document paths overlap with each other; must remove or rewrite one of these paths
putすれば置き換わると考えたが、putは例外こそ出ないが、startValueのItemに置き換えられてしまう。
任意の値でvalueをupdateしたり、任意のdeltaでインクリメント(デクリメント)する場合はStaticTableSchemaを利用してTableを作成する。
long value = 100;
long delta = 50;
StaticTableSchema<Item> schema =
StaticTableSchema.builder(Item.class)
.newItemSupplier(Item::new)
.addAttribute(String.class,
a -> a.name("ITEM_ID")
.getter(Item::getId)
.setter(Item::setId)
.tags(StaticAttributeTags.primaryPartitionKey()))
.addAttribute(Integer.class,
a -> a.name("TYPE")
.getter(Item::getType)
.setter(Item::setType)
.tags(StaticAttributeTags.primarySortKey()))
.addAttribute(Long.class,
a -> a.name("VALUE")
.getter(Item::getValue)
.setter(Item::setValue)
.tags(StaticAttributeTags.atomicCounter(value, delta)))
.build();
DynamoDbEnhancedClientからこのスキーマを利用して作成したDynamoDbTableに対してput要求/update要求を行うことで任意の値でvalueをupdateしたり、任意のdeltaでインクリメント(デクリメント)することができる。(若干無理やりではある)
任意の値でvalueを更新したい場合は、StaticTableSchemaに渡すvalueを任意の値にしてput要求すれば良い。
任意のdeltaでインクリメント(デクリメント)したい場合は、StaticTableSchemaに渡すdeltaを任意の値にしてupdate要求すれば良い。
補足
ちなみにStaticTableSchemaを利用している場合でも、@DynamoDbAtomicCounterアノテーションは付与したままで良い。
なぜなら、通常の場合@DynamoDbBeanが付与されたクラスからスキーマを生成し、DynamoDbEnhancedClientからこのスキーマを利用してDynamoDbTableを作成する。
TableSchema.fromBean(Item.class)
StaticTableSchemaを利用する場合は、Itemクラスに付与しているアノテーションは全く使われないので、共存できるということになる。
感想
@DynamoDbAtomicCounterアノテーションは利用しているサンプルが少なかったり、Googleで検索しても情報が全然出てこない。
基本すぎるからなのか、それとも誰も使おうとしていないのか分からないが、公式でも紹介しているような処理だった。
AWS SDK v2・・・まだまだ奥が深い・・・。