DynamoDB

1 はじめに

1.1 KVSとは

KVSとはKey-Value-Storeのことで、その名の通りキーと値の関係でデータを保存する形式を表します。リレーショナルデータベースはテーブルという形でデータを保存する形式でした。それぞれの方法で社員データを扱うとどうなるか見てみましょう。

RDBでは正規化を行うことでテーブルを分け、リレーションを作成してデータを管理します。それに対して、KVSの場合は一つの情報は一つのレコードとして保存します。RDBと比較して、KVSには次のような利点が挙げられます。

ビッグデータの重要性が高まる昨今の需要に応えることをKVSは期待されています。

1.2 DynamoDB

DynamoDBはAmazon社が提供するKey Value データベースです。

https://aws.amazon.com/jp/dynamodb/

次のような特徴があります。

スケールに応じたパフォーマンス

DynamoDB は、規模に関係なく、一貫した数ミリ秒台の応答時間を実現することで、世界最大規模のアプリケーションの一部をサポートしています。事実上無制限のスループットとストレージでアプリケーションを構築できます。DynamoDB グローバルテーブルでは、グローバルに分散しているアプリケーションのデータにローカルかつ高速にアクセスできるように、複数の AWS リージョン間でデータをレプリケートしています。レイテンシーがマイクロ秒の高速なアクセスを必要とするユースケースでは、DynamoDB Accelerator (DAX) は完全マネージド型のインメモリキャッシュを提供します。

サーバー管理が不要

DynamoDB はサーバーレスであり、プロビジョニング、パッチ適用、管理するサーバーはなく、インストール、保守、運用するソフトウェアもありません。DynamoDB はテーブルを自動的にスケールアップ/ダウンして容量を調整し、パフォーマンスを維持します。可用性とフォールトトレランス機能が組み込まれているため、こうした機能のためにアプリケーションを構築する必要はありません。DynamoDB では、プロビジョンドとオンデマンドのキャパシティモードがいずれも使用できるため、ワークロードごとに容量を指定するか、使用するリソースの分のみを支払うことでコストを最適化できます。

エンタープライズ対応

DynamoDB は、ビジネスクリティカルなアプリケーションを大規模に構築できるように ACID トランザクションをサポートしています。DynamoDB はデフォルトですべてのデータを暗号化しており、すべてのテーブルに対してきめ細かい ID とアクセスコントロールを提供します。数百テラバイトのデータを完全にバックアップして、テーブルのパフォーマンスに影響を及ぼすことなく、直前の 35 日間の任意の時点に復元できます。ダウンタイムもありません。また、DynamoDB は、可用性を保証するためのサービスレベルアグリーメントでもサポートされています。


1.3 用語

DynamoDBで使用される用語はRDBとは異なる場合があります。RDB、DynamoDB、MongoDBで使われる用語の違いを見てみましょう。

用語の違い

RDB

DynamoDB

MongoDB

テーブル

テーブル

コレクション

レコード(行)

項目

ドキュメント

フィールド(列)

属性

フィールド

プライマリキー

プライマリキー

オブジェクトID

インデックス

セカンダリインデックス

インデックス

ビュー

グローバルセカンダリインデックス

ビュー

パーティションキーとソートキー

DynamoDBでは高速アクセスを実現するために、パーティションキーとソートキーという仕組みを採用しています。パーティションキーを設定すると、パーティションごとに分けて項目を保存します。例えば、部署名をパーティションキーにして以下のデータ保存するとしましょう。

部署名

No

名前

生年月日

性別

営業部

001

クロノス太郎

1990-01-01

M

営業部

002

クロノス花子

1989-01-01

F

営業部

003

クロノス健二

1991-01-01

M

開発部

001

クロノス陽子

1981-01-01

F

開発部

002

クロノス隆司

1985-01-01

M

部署ごとにパーティションを作り、以下の図のような形で保存されます。

項目をパーティションで分けることで高速な検索を可能にします。そのため、DynamoDBではパーティションキーにどの属性を指定するかが非常に重要です。パーティションキーは必ず設定する必要があります。

ソートキーはパーティション内での項目の並び順に使われます。これも高速な検索を可能にするための設定です。先ほどの図例ではNo順に項目が並んでいるため、ソートキーにNoを指定していることがわかります。

また、DynamoDBではパーティションキーとソートキーで合わせてプライマリキーとします。パーティションキーのみでプライマリキーとすることもできますが、パーティションのメリットを受けられなくなるため、多用は避けるべきでしょう。(その場合、ソートキーは設定不要)


2 DynamoDBの操作

DynamoDBを作成しコンソール上で操作してみましょう。

2.1 AWSアカウントの作成

DynamoDBを使用するためには、まずAWS(Amazon Web Services)のアカウントを作成する必要があります。以下のURLから登録ができます。

https://portal.aws.amazon.com/billing/signup#/start

※AWSのいくつかのサービスは有料であるため、アカウント作成時にクレジットカードの登録を求められます。

※本テキストでは無料利用枠の範囲内でDynamoDBを使用するため、請求は発生しません。

2.2 テーブル作成

ログインするとAWSサービスの一覧画面が表示されます。画面上部にある検索ボックスにDynamoDBと入力しEnterキーを押下します。


DynamoDBの画面が表示されるので、「テーブルの作成」を選択します。

DynamoDBのテーブルを作成する画面が表示されます。テーブル名とプライマリキーを入力します。プライマリキーは “001”など、左ゼロ埋めにしたいので文字列にします。DynamoDBではプライマリキーの設定は必須です。入力が完了したら「作成」ボタンを押下します。パーティションキーとソートキーを合わせて複合主キーにしましょう。


テーブルの作成が完了するとテーブルの概要画面が表示されます。

以降は左上の「テーブルの作成」ボタンを押下することで、同様にテーブルの作成を行うことができます。

各タブについて

DynamoDBではタブごとに様々な管理を行うことができます。


2.3 項目の追加

DynamoDBの項目はRDBのレコードにあたります。上部のタブから項目タブを選択してみましょう。項目画面が表示されるので、「項目の作成」ボタンを押下します。項目の作成画面が表示されるので項目を以下のように作成して保存してみましょう。

※DynamoDBでは日付型がないのでBirthdayにはStringを指定します。

更に従業員情報を登録してみましょう。項目の作成画面の左上の「Tree」を「Text」に変更するとJSON形式で入力することができます。「Text」に変更して以下の項目を追加しましょう。

{

  "Birthday": "1989-01-01",

  "DeptName": "営業部",

  "Gender": "F",

  "Name": "クロノス花子",

  "No": "002"

}

{

  "Birthday": "1991-01-01",

  "DeptName": "営業部",

  "Gender": "M",

  "Name": "クロノス健二",

  "No": "003"

}

{

  "Birthday": "1981-01-01",

  "DeptName": "開発部",

  "Gender": "F",

  "Name": "クロノス陽子",

  "No": "001"

}

{

  "Birthday": "1985-01-01",

  "DeptName": "開発部",

  "Gender": "M",

  "Name": "クロノス隆司",

  "No": "002"

}

※1件ずつしか保存できません。

2.4 スキャンとクエリ

DynamoDBで項目を取得する方法はスキャンとクエリの2種類があります。クエリはまずパーティションを指定し、パーティションの中から項目をフィルタリングします。スキャンはテーブル全体から検索します。スキャンを実行すると、テーブルの全ての項目のデータ属性を返します。スキャンではフィルターを設定することができます。以下のように設定して「開始」ボタンを押下します。

クエリを実行するには、「スキャン」の代わりに「クエリ」を選択します。クエリを実行する場合は、どのパーティションから検索するか選び、ソートキーで絞り込む必要があります。

クエリでも「フィルターの追加」からフィルターを追加することが可能です。スキャンよりもクエリの方が効率が良いので極力クエリを使用するようにしましょう。

2.5 項目の編集

項目を編集したい場合は、カーソルを合わせると表示されるペンのマークを押下します。

項目の編集画面から変更することができます。

項目一覧でNameが変更されていることを確認してください。


2.6 項目の削除

項目の削除をするには、項目一覧の削除したい項目にチェックをつけ、左上の「アクション」ボタンから「削除」を選択します。

ポップアップで警告文が表示され、「削除」ボタンを押下すると削除が実行されます。


2.7 テーブルの削除

最後にテーブルの削除をしてみましょう。テーブル自体を削除するには、左ペインのテーブル一覧で削除したいテーブルを選択して、「テーブルの削除」ボタンを押下します。

ポップアップで警告文が表示され、「削除」ボタンを押下すると削除が実行されます。


3 JavaによるDynamoDBの操作

DynamoDBは多くの言語のAPIを提供しています。本テキストではJavaを使用します。

3.1 IAMユーザーの作成

IAMユーザーとは、Identity and Access Management Userの略でAWSのリソースを使用するための権限が付与されたユーザーです。外部からDynamoDBを操作するためには操作するための権限を持つIAMユーザーを作成する必要があります。

サービス一覧画面に戻り、検索ボックスにIAMと入力しEnterキーを押下します。

左メニューから「ユーザー」を選択し、「ユーザーを追加」をクリックします。

「ユーザー名」に”MyDynamoUser”、「アクセスの種類」で「プログラムによるアクセス」にチェックをつけて、「次のステップ:アクセス権限」を押下します。

「既存のポリシーを直接アタッチ」を選択し、「ポリシーのフィルタ」に”Dynamo”と入力します。ポリシーの一覧が絞り込まれるので、「AmazonDynamoDBFullAccess」を選択し、「次のステップ:タグ」を押下します。

タグの追加は今回は行いませんので、「次のステップ:確認」を押下します。

内容を確認し「ユーザーの作成」を押下します。

ユーザーが作成されるので、「.csvのダウンロード」を押下し、画面を閉じます。

ダウンロードしたCSVファイルはDynamoDBにアクセスするときに必要なので、削除しないようにしてください。


3.2 AWS SDKのロード

DynamoDBにアクセスするためのAPIを利用するにはAWS SDKをプロジェクトにロードします。本テキストではMavenを使用します。

MavenでJavaプロジェクトを作成したあと、pom.xmlのdependenciesにAWS SDKを追加します。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>com.example</groupId>

  <artifactId>dynamo</artifactId>

  <version>0.0.1-SNAPSHOT</version>

  <packaging>jar</packaging>

  <name>dynamo</name>

  <url>http://maven.apache.org</url>

  <properties>

    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

  </properties>

  <dependencies>

    <dependency>

      <groupId>com.amazonaws</groupId>

      <artifactId>aws-java-sdk</artifactId>

      <version>1.11.734</version>

    </dependency>

  </dependencies>

</project>

(参考)Mavenリポジトリ

https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk/1.11.734


3.3 AWS Toolkit for Eclipseのインストール

EclipseでAWS SDKを扱う場合、AWS Tool Kit for Eclipseをインストールすると設定が簡単になります。

AWS Tool Kit for Eclipse

https://aws.amazon.com/jp/eclipse/

Eclipseの「Window」メニューから「Eclipse Marketpalce」を選択し、検索ボックスに”aws toolkit”と入れて検索します。「AWS Toolkit for Eclipse 2.0」の「install」ボタンを押下します。

今回は「AWS Toolkit for Eclipse Core」だけが必要なので、他のチェックボックスは外して「Confirm」ボタンを押下します。

インストールが完了したらEclipseの再起動を促されるので、再起動します。

再起動後、アクセスキーの設定を求められるので、IAMユーザーのところでダウンロードしたアクセスキーを設定し、「Finish」ボタンを押下します。

3.4 接続

Movieテーブルを操作するMovieDaoクラスを作成します。

package com.example.dynamo.dao;

import com.amazonaws.client.builder.AwsClientBuilder;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;

import com.amazonaws.services.dynamodbv2.document.DynamoDB;

public class MovieDao {

    private DynamoDB dynamoDB;

 

    public MovieDao() {

        String endpoint = "http://dynamodb.ap-northeast-1.amazonaws.com";

        String region = "ap-northeast-1";

        AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard()

        .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endpoint, region))

        .build();

        this.dynamoDB = new DynamoDB(client);

    }

}

MovieDaoクラスのコンストラクタで接続の処理を実装します。

エンドポイントとリージョンにはDynamoDBを作成したリージョンを設定します。

東京の場合は、“ap-northeast-1”を設定します。


3.5 テーブルの作成

次にMovieテーブルを作成するようにDynamoDBに指示を出します。

package com.example.dynamo.dao;

import java.util.Arrays;

import com.amazonaws.client.builder.AwsClientBuilder;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;

import com.amazonaws.services.dynamodbv2.document.DynamoDB;

import com.amazonaws.services.dynamodbv2.document.Table;

import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;

import com.amazonaws.services.dynamodbv2.model.KeySchemaElement;

import com.amazonaws.services.dynamodbv2.model.KeyType;

import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;

import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType;

public class MovieDao {

    private DynamoDB dynamoDB;

   

    // コンストラクタ省略

        

    public void create()throws InterruptedException {

        System.out.println("Attempting to create table; please wait...");

       

        Table table = dynamoDB.createTable("Movies",

        Arrays.asList(new KeySchemaElement("year", KeyType.HASH), // Partitionkey

                            new KeySchemaElement("title", KeyType.RANGE)), // Sort key

        Arrays.asList(new AttributeDefinition("year", ScalarAttributeType.N),

                            new AttributeDefinition("title", ScalarAttributeType.S)),

                            new ProvisionedThroughput(10L, 10L));

        table.waitForActive();

       

        System.out.println("Success.  Table status: " + table.getDescription().getTableStatus());            

    }

}


Mainメソッドを実行するクラスを作成し、createメソッドを呼び出します。

package com.example.dynamo;

import com.example.dynamo.dao.MovieDao;

public class App {

    public static void main( String[] args ) throws Exception {

       

        MovieDao movieDao = new MovieDao();

       

        movieDao.create();

    }

}

実行完了したらコンソールを確認してみましょう。Moiveテーブルが作成されているのが確認できます。


DynamoDBのプロビジョニング設定について

DynamoDBはスムーズなスケーリングを実現するための仕組みとしてプロビジョニングという考え方を導入しています。プロビジョニングとは、リソースへの通信量を予測し、必要なネットワークやコンピュータの設備を事前に準備しておく考え方です。対して必要な時に必要な分だけ使用する考え方をオンデマンドと読んでいます。

プロビジョニングには無料利用枠が設定されており、一定値を超えなければ無料でDynamoDBを利用することができます。

キャパシティーユニット

DynamoDBではアクセスをキャパシティーユニットという単位で管理しています。WCU(書き込みキャパシティーユニット)とRCU(読み込みキャパシティーユニット)があります。

用語

説明

WCU

(Write Capacity Unit)

1秒間に書き込みできる回数。

5ユニットの場合、1秒間に書き込みが5回可能。

月あたり25WCUまで無料利用枠。

RCU

(Read Capacity Unit)

1秒間に書き込みできる回数。

5ユニットの場合、1秒間に結果整合性のある読み込みが10回可能。

強力な整合性のある読み込みの場合は5回の読み込みが可能。

月あたり25RCUまで無料利用枠。

結果整合性:整合性が取れている状態。ただし、全ての更新が反映されているとは限らない。

強力な整合性:整合性が取れている状態。全ての更新が反映されている。

設定したキャパシティーユニットを上回るアクセスがあった場合に告知するアラートの設定をすることができます。


3.6 項目の登録

次にMovieテーブルに項目を追加してみましょう。以下のURLからJSONファイルをダウンロードします。

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/samples/moviedata.zip

解凍したファイルをプロジェクトに追加します。


moviedata.jsonを読み込んでテーブルに登録するputメソッドを定義します。

package com.example.dynamo.dao;

import java.io.IOException;

import java.util.Arrays;

import java.util.Iterator;

import com.amazonaws.client.builder.AwsClientBuilder;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;

import com.amazonaws.services.dynamodbv2.document.DynamoDB;

import com.amazonaws.services.dynamodbv2.document.Item;

import com.amazonaws.services.dynamodbv2.document.Table;

import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;

import com.amazonaws.services.dynamodbv2.model.KeySchemaElement;

import com.amazonaws.services.dynamodbv2.model.KeyType;

import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;

import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType;

import com.fasterxml.jackson.databind.JsonNode;

import com.fasterxml.jackson.databind.node.ObjectNode;

public class MovieDao {

    private DynamoDB dynamoDB;

    private Table table;

    public MovieDao() {

        // 省略

        this.table = dynamoDB.getTable("Movies");   // 追加

    }

   

    // createメソッド省略

        

    public void put(JsonNode node) throws IOException {

        Iterator<JsonNode> iter = node.iterator();

       

        ObjectNode currentNode;        

        while (iter.hasNext()) {

            currentNode = (ObjectNode) iter.next();      

            int year = currentNode.path("year").asInt();

            String title = currentNode.path("title").asText();

            this.table.putItem(new Item().withPrimaryKey("year", year, "title", title)

                                                      .withJSON("info", currentNode.path("info").toString()));

            System.out.println("PutItem succeeded: " + year + " " + title);

        }

    }

}

メインメソッドからputメソッドを呼び出してみましょう。

package com.example.dynamo;

import java.io.File;

import com.example.dynamo.dao.MovieDao;

import com.fasterxml.jackson.core.JsonFactory;

import com.fasterxml.jackson.core.JsonParser;

import com.fasterxml.jackson.databind.JsonNode;

import com.fasterxml.jackson.databind.ObjectMapper;

public class App {

    public static void main( String[] args ) throws Exception {

        MovieDao movieDao = new MovieDao();

        try (JsonParser parser = new JsonFactory().createParser(new File("data/moviedata.json"))) {

            JsonNode node = new ObjectMapper().readTree(parser);

            movieDao.put(node);                    

        }

    }

}

実行が完了したらコンソールを確認しましょう。Movieのアイテムが登録されていることを確認できます。


3.7 項目の取得

次にMovieテーブルから項目を取得してみましょう。

プライマリキーによる取得

package com.example.dynamo.dao;

import java.io.IOException;

import java.util.Arrays;

import java.util.Iterator;

import com.amazonaws.client.builder.AwsClientBuilder;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;

import com.amazonaws.services.dynamodbv2.document.DynamoDB;

import com.amazonaws.services.dynamodbv2.document.Item;

import com.amazonaws.services.dynamodbv2.document.Table;

import com.amazonaws.services.dynamodbv2.document.spec.GetItemSpec;

import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;

import com.amazonaws.services.dynamodbv2.model.KeySchemaElement;

import com.amazonaws.services.dynamodbv2.model.KeyType;

import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;

import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType;

import com.fasterxml.jackson.databind.JsonNode;

import com.fasterxml.jackson.databind.node.ObjectNode;

public class MovieDao {

    // 省略

    public Item getByPrimaryKey(int year, String title) {

                

        GetItemSpec spec = new GetItemSpec().withPrimaryKey("year", year, "title", title);

        

        return this.table.getItem(spec);

    }

}


メインメソッドからgetByPrimaryKeyメソッドを呼び出してみましょう。

package com.example.dynamo;

import com.amazonaws.services.dynamodbv2.document.Item;

import com.example.dynamo.dao.MovieDao;

public class App {

    public static void main( String[] args ) throws Exception {

        MovieDao movieDao = new MovieDao();

       

        int year = 1933;

        String title = "King Kong";

            

        Item movie = movieDao.getByPrimaryKey(year, title);

        System.out.println(movie);

    }

}

取得されたKey-Valueデータが出力されます。


クエリによる取得

クエリを使って項目を取得するには、パーティションキーであるyearを指定する必要があります。

package com.example.dynamo.dao;

import java.io.IOException;

import java.util.Arrays;

import java.util.HashMap;

import java.util.Iterator;

import com.amazonaws.client.builder.AwsClientBuilder;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;

import com.amazonaws.services.dynamodbv2.document.DynamoDB;

import com.amazonaws.services.dynamodbv2.document.Item;

import com.amazonaws.services.dynamodbv2.document.ItemCollection;

import com.amazonaws.services.dynamodbv2.document.QueryOutcome;

import com.amazonaws.services.dynamodbv2.document.Table;

import com.amazonaws.services.dynamodbv2.document.spec.GetItemSpec;

import com.amazonaws.services.dynamodbv2.document.spec.QuerySpec;

import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;

import com.amazonaws.services.dynamodbv2.model.KeySchemaElement;

import com.amazonaws.services.dynamodbv2.model.KeyType;

import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;

import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType;

import com.fasterxml.jackson.databind.JsonNode;

import com.fasterxml.jackson.databind.node.ObjectNode;

public class MovieDao {

    // 省略

    public ItemCollection<QueryOutcome> queryBetweenLetter1AndLetter2(int year, String letter1, String letter2) {

        HashMap<String, String> nameMap = new HashMap<String, String>();

        nameMap.put("#yr", "year");                

        

        HashMap<String, Object> valueMap = new HashMap<String, Object>();

        valueMap.put(":yyyy", year);

        valueMap.put(":letter1", letter1);

        valueMap.put(":letter2", letter2);

       

        String queryString = "#yr = :yyyy and title between :letter1 and :letter2";

       

        QuerySpec querySpec = new QuerySpec()

            .withKeyConditionExpression(queryString)

            .withNameMap(nameMap)

            .withValueMap(valueMap);

     

        ItemCollection<QueryOutcome> items = this.table.query(querySpec);

        return items;

    }

}

メインメソッドからqueryBetweenLetter1AndLetter2メソッドを呼び出してみましょう。

package com.example.dynamo;

import com.amazonaws.services.dynamodbv2.document.Item;

import com.amazonaws.services.dynamodbv2.document.ItemCollection;

import com.amazonaws.services.dynamodbv2.document.QueryOutcome;

import com.amazonaws.services.dynamodbv2.document.internal.IteratorSupport;

import com.example.dynamo.dao.MovieDao;

public class App {

    public static void main( String[] args ) throws Exception {

        MovieDao movieDao = new MovieDao();

            

        int year = 1992;

        String letter1 = "A";

        String letter2 = "L";

       

        ItemCollection<QueryOutcome> outcome =

                movieDao.queryBetweenLetter1AndLetter2(year, letter1, letter2);

       

        IteratorSupport<Item, QueryOutcome> iterator = outcome.iterator();

            

        Item item = null;

       

        while (iterator.hasNext()) {

            item = iterator.next();

            System.out.println(item.getNumber("year") + ": "  + item.getString("title"));

        }

    }

}

取得された項目の発行年とタイトルが表示されます。


スキャンによる取得

パーティションをまたいで項目を取得したい場合はスキャンを利用します。

package com.example.dynamo.dao;

import java.io.IOException;

import java.util.Arrays;

import java.util.HashMap;

import java.util.Iterator;

import com.amazonaws.client.builder.AwsClientBuilder;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;

import com.amazonaws.services.dynamodbv2.document.DynamoDB;

import com.amazonaws.services.dynamodbv2.document.Item;

import com.amazonaws.services.dynamodbv2.document.ItemCollection;

import com.amazonaws.services.dynamodbv2.document.QueryOutcome;

import com.amazonaws.services.dynamodbv2.document.Table;

import com.amazonaws.services.dynamodbv2.document.spec.GetItemSpec;

import com.amazonaws.services.dynamodbv2.document.spec.QuerySpec;

import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;

import com.amazonaws.services.dynamodbv2.model.KeySchemaElement;

import com.amazonaws.services.dynamodbv2.model.KeyType;

import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;

import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType;

import com.fasterxml.jackson.databind.JsonNode;

import com.fasterxml.jackson.databind.node.ObjectNode;

public class MovieDao {

    // 省略

    public ItemCollection<ScanOutcome> scanBetweenYear1AndYear2(int year1, int year2) {

        ScanSpec scanSpec = new ScanSpec()

            .withFilterExpression("#yr between :start_yr and :end_yr")

            .withNameMap(new NameMap().with("#yr", "year"))

            .withValueMap(new ValueMap().withNumber(":start_yr", 1950).withNumber(":end_yr", 1959));

        ItemCollection<ScanOutcome> items = table.scan(scanSpec);

               

        return items;

    }

}


メインメソッドからscanBetweenYear1AndYear2を呼び出します。

package com.example.dynamo;

import com.amazonaws.services.dynamodbv2.document.Item;

import com.amazonaws.services.dynamodbv2.document.ItemCollection;

import com.amazonaws.services.dynamodbv2.document.ScanOutcome;

import com.amazonaws.services.dynamodbv2.document.internal.IteratorSupport;

import com.example.dynamo.dao.MovieDao;

public class App {

    public static void main( String[] args ) throws Exception {

        MovieDao movieDao = new MovieDao();

            

        int year1 = 1950;

        int year2 = 1959;

            

        ItemCollection<ScanOutcome> outcome =

                movieDao.scanBetweenYear1AndYear2(year1, year2);

        IteratorSupport<Item, ScanOutcome> iterator = outcome.iterator();

            

        Item item = null;

        while (iterator.hasNext()) {

            item = iterator.next();

            System.out.println(item.getNumber("year") + ": " + item.getString("title"));

        }

    }

}

フィルター条件に一致する全ての項目が取得できているのがわかります。


3.8 項目の更新

プライマリキーによる更新

プライマリキーを条件に1件の項目を更新します。

// 省略

public class MovieDao {

   

    // 省略

    public UpdateItemOutcome updateRatingAndActors(int year, String title, double rating, List<String> actors) {

        UpdateItemSpec updateItemSpec = new UpdateItemSpec()

                .withPrimaryKey("year", year, "title", title)

                .withUpdateExpression("set info.rating = :r, info.actors=:a")

                .withValueMap(new ValueMap().withNumber(":r", rating).withList(":a", actors))

                .withReturnValues(ReturnValue.UPDATED_NEW);

        UpdateItemOutcome outcome = table.updateItem(updateItemSpec);

        return outcome;

    }

}

メインメソッドからupdateRatingAndActorsメソッドを呼び出します。

// 省略

public class App {

    public static void main( String[] args) throws Exception {

        MovieDao movieDao = new MovieDao();

        int year = 1933;

        String title = "King Kong";

        double rating = 5.5;

        List<String> actors = Arrays.asList("Larry", "Moe", "Curly");

       

        UpdateItemOutcome outcome = movieDao.updateRatingAndActors(year, title, rating, actors);

            

        System.out.println("UpdateItem succeeded:\n" + outcome.getItem().toJSONPretty());

    }

}

コンソール画面でKing Kongのratingとactorsが更新されていることを確認してみましょう。

条件付き更新

更新時に条件をつけることができます。条件を満たしていない場合は更新は行われません。

// 省略

public class MovieDao {

   

    // 省略

    public UpdateItemOutcome updateRatingAndActors(int year, String title, double rating, List<String> actors) {

        UpdateItemSpec updateItemSpec = new UpdateItemSpec()

                     .withPrimaryKey("year", year, "title", title)

                .withUpdateExpression("set info.rating = :r, info.actors=:a")

                .withConditionExpression("size(info.actors) > :num") // 追加

                .withValueMap(new ValueMap().withNumber(":r", rating).withList(":a", actors)

                                                            .withNumber(":num", 3))  // 追加

                .withReturnValues(ReturnValue.UPDATED_NEW);

        UpdateItemOutcome outcome = table.updateItem(updateItemSpec);

        return outcome;

    }

}

Mainメソッドの変更は不要です。実行するとConditionalCheckFailedExceptionがthrowされます。King Kongのactorsの要素数は3ですが、条件で「actorsの要素数が3より大きい」を追加しているためです。


3.8 項目の削除

プライマリキーによる削除

プライマリキーを条件に項目を一件削除します。

// 省略

public class MovieDao {

    // 省略

    public void delete(int year, String title) {

            DeleteItemSpec deleteItemSpec = new DeleteItemSpec()

                .withPrimaryKey(new PrimaryKey("year", year, "title", title))

                .withConditionExpression("info.rating <= :val")

                .withValueMap(new ValueMap().withNumber(":val", 5.5));

            table.deleteItem(deleteItemSpec);

    }

}

メインメソッドからdeleteメソッドを呼び出します。

package com.example.dynamo;

import com.example.dynamo.dao.MovieDao;

public class App {

        public static void main( String[] args) throws Exception {

        MovieDao movieDao = new MovieDao();

            

        int year = 1933;

        String title = "King Kong";

       

        movieDao.delete(year, title);

    }

}

withConditionExpressionを指定していましたが、今回は条件を満たしていたので削除が実行されます。コンソール画面で確認してください。