Amazon DynamoDB Local & AWS SDK for Java を使ってみよう(後編)

Pocket

こんにちは。松本です。
TECHSCORE Advent Calendar 2014 の 24 日目の投稿です。

前回は eclipse 上で Amazon DynamoDB Local をインストールし、AWS SDK for Java からアイテムの保存を行いました。今回は AWS SDK for Java にフォーカスをあて基本的な API を利用してみます。

環境は引き続き前回セットアップした DynamoDB Local を使います。

高レベル API と低レベル API

PutItemRequest クラスを使った前回のサンプルコードは「低レベル API」と呼ばれる API で、AWS SDK for Java では DynamoDB を操作するために「高レベル API」を選択することも可能です。

AWS SDK for Java の使用

低レベル API
低レベル API は、基本的な DynamoDB オペレーションに緊密に対応しています。低レベル API を使用すると、DynamoDB オペレーションを使用して実行できるオペレーション(テーブルの作成、更新、削除、および項目の作成、読み込み、更新、削除など)と同じものを実行できます。

高レベル API
高レベル API では、オブジェクト永続性プログラミング手法を使用して、Java オブジェクトを DynamoDB テーブルと属性にマッピングします。高レベル API を使用してテーブルを作成することはできませんが、テーブル項目の作成、読み込み、更新、および削除は可能です。

普段、リレーショナルデータベースへのアクセスに ORM を使う機会が多い方には高レベル API の方が馴染みやすいと思います。

では前回のサンプルコードを高レベル API で書くとどうなるでしょう。

高レベル API でアイテムを保存する

高レベル API では POJO を DynamoDB のテーブルにマッピングし、DynamoDBMapper クラスで操作します。

1. マッピング

まずは前回のサンプルで使用した Channels テーブルを Channel クラスにマッピングします。

com/techscore/dynamodb_local_sample/Channel.java

実際は各フィールドに対するプロパティも必要ですので適宜、Getter / Setter メソッドを定義して下さい。

マッピング方法は見たまま。@DynamoDBTable アノテーションでクラスとテーブルのマッピングを行い、@DynamoDBHashKey アノテーションで HashKey を、@DynamoDBAttribute アノテーションで属性をマッピングしています。

もし RangeKey をマッピングしたいなら @DynamoDBRangeKey アノテーションを、マッピングしたくないフィールドやプロパティがあれば、@DynamoDBIgnore アノテーションを使います。

@DynamoDBMarshalling アノテーションは高レベル API で属性のデータ型としてサポートされていないクラスを扱うためのアノテーションです。上記例では java.time.OffsetDateTime を扱うため、独自に作成した OffsetDateTimeMarshaller クラスを使うことを定義しています。

OffsetDateTimeMarshaller クラスは DynamoDBMarshaller インターフェース(javadoc)を実装した次のようなクラス定義となります。

com/techscore/dynamodb_local_sample/OffsetDateTimeMarshaller.java

2. データの操作

高レベル API でのデータ操作は非常にシンプル。アイテムを保存するだけなら DynamoDBMappersave() メソッドにオブジェクトを渡すだけです。

com/techscore/dynamodb_local_sample/ChannelCreationHighLevelAPI.java

DynamoDBClient の生成までは低レベル API の時と同じです。

尚、DynamoDBMapper オブジェクトはスレッドセーフなので、実際のアプリケーションではスレッド間で共有することも可能です。

DynamoDBMapper

This class is thread-safe and can be shared between threads. It's also very lightweight, so it doesn't need to be.

Conditional Writes

DynamoDB では Conditional Writes を使うことで、アイテムや属性の更新を条件付きで実行することが可能です。

Conditional Writes

To help clients coordinate writes to data items, DynamoDB supports conditional writes for PutItem, DeleteItem, and UpdateItem operations. With a conditional write, an operation succeeds only if the item attributes meet one or more expected conditions; otherwise it returns an error.

これはマルチユーザー環境で同じ属性に同時アクセスされた時のデータの整合性を制御するものです。今回は身近なサンプルとして、同じプライマリキーを持つアイテムが存在しない場合のみアイテムを新規保存するコードを考えてみます。

実は低レベル API の PutItemRequest や高レベル API の save() メソッドは一致するプライマリキーを持つアイテムが存在しなければアイテムを新規保存し、存在すればアイテムや属性を更新します。これを新規保存のみの操作に限定してみましょう。

1. 低レベル API での例

低レベル API でのサンプルとして前回のサンプルコードを流用します。

アイテム保存時の条件に「テーブル内に新しいアイテムの channelName 属性値と一致するアイテムが無い」という条件を加えるには PutItemRequest オブジェクトに addExpectedEntry() を追加します。

ExpectedAttributeValue オブジェクトに withExists(false) とすることで「指定した属性値が一致するアイテムが存在しないこと」という条件を表現しています。

ソースコード全体としてはこうなります。

com/techscore/dynamodb_local_sample/ChannelCreationLowLevelAPI.java

このコードを実行すると、Channels テーブル内に channelName 属性値が "techscore" であるアイテムが存在する場合、ConditionalCheckFailedException がスローされアイテムの保存に失敗します。

2. 高レベル API での例

高レベル API では DynamoDBSaveExpression クラスの withExpectedEntry() メソッドを使います。

低レベル API の時と同様、ExpectedAttributeValuewithExists(false) とすることで、指定した属性に該当するアイテムが存在しないことという条件を表現しています。

また、DynamoDBMappersave() メソッドは 2 つ目の引数として DynamoDBSaveExpression オブジェクトを渡せるものを使います。

ソースコード全体はこうなります。

ChannelCreationHighLevelAPI.java

Atomic Counters

Atomic Counters はアイテム更新時に属性値をアトミックにインクリメント/デクリメントする機能です。

Atomic Counters

DynamoDB supports atomic counters, where you use the UpdateItem operation to increment or decrement the value of an existing attribute without interfering with other write requests.

この機能は現時点で高レベル API には実装されておらず、低レベル API でのみ利用可能となっています。

今回のサンプルでは UpdateItemRequest を使います。

UpdateItemRequestPutItemRequest と違い、更新時にアイテム全体を置き換えるのではなく、指定した属性のみを更新してくれます(高レベル API でも save() メソッドに引数として DynamoDBMapperConfig オブジェクトを渡すことで保存時の挙動を変えることができます)。

まずはソースコード全体から。

com/techscore/dynamodb_local_sample/ChannelModificationLowLevelAPI.java

このコードでは Conditional Writes でプライマリキーが一致するアイテムが存在する場合のみ属性を更新するよう制限し、Atomic Counters で maxMessageNumber 属性値を増分 1 でインクリメントしています。

AttributeValueUpdate クラスの withAction() メソッドに AttributeAction.ADD を渡すことで属性値をインクリメントすることを指示します。その後の withValue() はインクリメントの増分が 1 であることを指定しています。

PutItemRequest の時と同様に、addExpectedEntry() メソッドで Conditional Writes に関する操作を行っています。

withReturnValues() メソッドは、DynamoDBClientupdateItem() メソッドの戻り値に関する設定で、ReturnValue.UPDATED_NEW を指定することで更新後のアイテムを戻り値にセットするよう指定しています。

次のようにすることで、インクリメントされた maxMessageNumber 属性値を取得できます。

AttributeValue での値操作では全般的に String しか使えないのが残念なところです・・・。

最後に

今回は DynamoDB Local とは関係のない記事になってしまいました。

Pocket

Comments are closed, but you can leave a trackback: Trackback URL.