LoginSignup
23
23

More than 5 years have passed since last update.

iOSからのDynamoDB使用方法について

Posted at

AWS Mobile SDK for iOSを使ってDynamoDBを操作する場合の方法を公式ドキュメントを参考にまとめてみました。

前提条件

iOSでDynamoDBを使うために、以下の状態を満たす必要があります
* Amazon Mobile SDK for iOSを入れている (Ver2.0.17)
* Cognitoでの認証処理を実装済
* DynamoDBにテーブルが存在する
* DynamoDBのパーミッションの設定済

公式ドキュメント

AWS Mobile SDK Developer Guides

SDKリファレンス

ポイント

  1. DynamoDBのオブジェクトマッパークライアントを使って操作
  2. テーブルマッピングクラスでテーブル情報や取得結果を管理

DynamoDBのオブジェクトマッパークライアント

『AWSDynamoDBObjectMapper』クラスを使用すると簡単にDynamoDBを操作できます。
V1までは、低レベルクライアントクラス(AWSDynamoDB)を使って実装する必要がありましたが、
かなり面倒でした。
AWSDynamoDBObjectMapperクラスはその面倒な部分をやってくれています。

リファレンスはこちら

生成方法

AWSDynamoDBObjectMapper *dynamoDBObjectMapper = [AWSDynamoDBObjectMapper defaultDynamoDBObjectMapper];

このクラスで以下の操作ができます

操作 メソッド
GetItem - (BFTask *)load:(Class)resultClass hashKey:(id)hashKey rangeKey:(id)rangeKey
Query - (BFTask *)query:(Class)resultClass expression:(AWSDynamoDBQueryExpression *)expression
Scan - (BFTask *)scan:(Class)resultClass expression:(AWSDynamoDBScanExpression *)expression
PutItem / UpdateItem - (BFTask *)save:(AWSDynamoDBModel *)model
DeleteItem - (BFTask *)remove:(AWSDynamoDBModel *)model

逆に以下のことはできません。
低レベルAPI(クラスAWSDynamoDB)を使う必要があります。
※ Ver2.0.17時点

操作 実装方法
アトミックカウンタ AWSDynamoDBUpdateItemInputの updateExpression でアトミックカウンタを指定
AWSDynamoDBのupdateItemで更新実行
条件つき更新 AWSDynamoDBUpdateItemInputの conditionExpression で条件を指定し、
AWSDynamoDBのupdateItemで更新実行
BatchGetItem / BatchWriteItem AWSDynamoDBの batchGetItem/batchWriteItem を使う

テーブルのモデルクラス

AWSDynamoDBObjectMapperを使ってDynamoDBを操作する場合、
AWSDynamoDBObjectModelを継承したモデルクラスが必要となります。

このモデルクラスの役割は以下です

  • テーブルの定義情報を取得できるようにする
  • アイテム取得時の結果オブジェクトになる
  • アイテム削除や更新時の対象アイテムを指定する

モデルクラスを作る時のポイント

  • AWSDynamoDBObjectModelの継承
  • 属性名のプロパティを用意 (※全く同じ名前である必要がある)
  • AWSDynamoDBModelingプロトコルの実装(テーブル名、ハッシュキー、レンジキーを返すメソッドの実装)

Book.h

#import <Foundation/Foundation.h>
#import <AWSDynamoDB/AWSDynamoDB.h>

@interface Book : AWSDynamoDBObjectModel <AWSDynamoDBModeling>

@property (nonatomic, strong) NSString *Title;
@property (nonatomic, strong) NSString *Author;
@property (nonatomic, strong) NSNumber *Price;
@property (nonatomic, strong) NSString *ISBN;

@end

Book.m

#import <AWSDynamoDB/AWSDynamoDB.h>
#import "Book.h"

@implementation Book

+ (NSString *)dynamoDBTableName {
    return @"Books";
}

+ (NSString *)hashKeyAttribute {
    return @"ISBN";
}

@end

操作

Load (GetItem)

結果オブジェクトがモデルクラス(Book)の状態で取得できるのがいい

[[dynamoDBObjectMapper load:[Book class] hashKey:@"6543210987" rangeKey:nil]
continueWithBlock:^id(BFTask *task) {
        if (task.error) {
                NSLog(@"The request failed. Error: [%@]", task.error);
        }
        if (task.exception) {
                NSLog(@"The request failed. Exception: [%@]", task.exception);
        }
        if (task.result) {
                Book *book = task.result;
                //Do something with the result.
        }
        return nil;
}];

Query

AWSDynamoDBQueryExpressionを使ってキー条件を指定

// Queryの条件を指定
// ISBNが"3456789012"のアイテムを取得
AWSDynamoDBQueryExpression *queryExpression = [AWSDynamoDBQueryExpression new];
queryExpression.hashKeyValues = @"3456789012";

[[dynamoDBObjectMapper query:queryClass
                  expression:queryExpression]
 continueWithBlock:^id(BFTask *task) {
     if (task.error) {
         NSLog(@"The request failed. Error: [%@]", task.error);
     }
     if (task.exception) {
         NSLog(@"The request failed. Exception: [%@]", task.exception);
     }
     if (task.result) {
         // 取得するアイテムは複数の場合があるので、結果はAWSDynamoDBPaginatedOutputオブジェクト
         AWSDynamoDBPaginatedOutput *paginatedOutput = task.result;
         for (Book *book in paginatedOutput.items) {
             //Do something with book.
         }
     }
     return nil;
 }];

Scan

AWSDynamoDBScanExpressionを使って、Scanの条件を指定

// Scanの条件を指定
AWSDynamoDBScanExpression *scanExpression = [AWSDynamoDBScanExpression new];
scanExpression.limit = @10;

[[dynamoDBObjectMapper scan:[Book class]
                 expression:scanExpression]
 continueWithBlock:^id(BFTask *task) {
     if (task.error) {
         NSLog(@"The request failed. Error: [%@]", task.error);
     }
     if (task.exception) {
         NSLog(@"The request failed. Exception: [%@]", task.exception);
     }
     if (task.result) {
         // 取得するアイテムは複数の場合があるので、結果はAWSDynamoDBPaginatedOutputオブジェクト
         AWSDynamoDBPaginatedOutput *paginatedOutput = task.result;
         for (Book *book in paginatedOutput.items) {
             //Do something with book.
         }
     }
     return nil;
 }];

Save (PutItem / UpdateItem)

Book *myBook = [Book new];
myBook.ISBN = @"3456789012";
myBook.Title = @"The Scarlet Letter";
myBook.Author = @"Nathaniel Hawthorne";
myBook.Price = [NSNumber numberWithInt:899];
myBook.ViewCount = [NSNumber numberWithInt:0]; // 初期値を0でセット

[[dynamoDBObjectMapper save:myBook]
 continueWithBlock:^id(BFTask *task) {
     if (task.error) {
         NSLog(@"The request failed. Error: [%@]", task.error);
     }
     if (task.exception) {
         NSLog(@"The request failed. Exception: [%@]", task.exception);
     }
     if (task.result) {
         //Do something with the result.
     }
     return nil;
 }];

オプション

保存方法を指定できる

オプション名 説明
AWSDynamoDBObjectMapperSaveBehaviorUpdate(デフォルト) UpdateItem (Action:Put/Delete)
※ 属性と値を追加し、既存なら上書きする(Put)
※ 主キーの値がNSNullの場合、削除(Delete)
AWSDynamoDBObjectMapperSaveBehaviorUpdateSkipNullAttributes UpdateItem (Action:Put)
※ 主キーの値がNSNullの場合、何もしない
AWSDynamoDBObjectMapperSaveBehaviorAppendSet UpdateItem (Action:Add/Put)
※ 値がSet型の場合のみ要素を後ろに追加(Add)
※ それ以外は上書き(Put)
※ 本来は、Number型もAddの対象だが、このメソッドではできない
※ 主キーの値がNSNullの場合、何もしない
AWSDynamoDBObjectMapperSaveBehaviorClobber PutItem
※ アイテム(行)追加。既存のアイテムがあれば、そのアイテムの全属性が上書きされる

オプション指定方法

AWSDynamoDBObjectMapperConfigurationのsaveBehaviorに以下のいずれかを指定する
AWSDynamoDBObjectMapperConfigurationは、引数で渡すか、AWSDynamoDBObjectMapperのconfigurationプロパティに予め指定しておく

AWSDynamoDBObjectMapperConfiguration *updateMapperConfig = [AWSDynamoDBObjectMapperConfiguration new];
updateMapperConfig.saveBehavior = AWSDynamoDBObjectMapperSaveBehaviorUpdate_Skip_Null_Attributes;
// Update_Skip_Null_Attributes

Remove (DeleteItem)

Book *bookToDelete = [Book new];
bookToDelete.ISBN = @"4456789012";

[[dynamoDBObjectMapper remove:bookToDelete]
 continueWithBlock:^id(BFTask *task) {

     if (task.error) {
         NSLog(@"The request failed. Error: [%@]", task.error);
     }
     if (task.exception) {
         NSLog(@"The request failed. Exception: [%@]", task.exception);
     }
     if (task.result) {
         //Item deleted.
     }
     return nil;
 }];

アトミックカウンタ (インクリメント)

UpdateItemの更新文(UpdateExpression)を使って行います。
以下の例では、ViewCountに+1しています。

注意

アトミックカウントをするとき、属性がない場合、エラーとなりました。
ない場合は追加してくれると思っていましたが、そうはならず。。。
初期値を0で属性は用意しておく必要がありそうです。

公式ドキュメントによる条件つきアトミックカウンタの説明

// ViewCountに+1する (アトミックカウンタ)
AWSDynamoDB *dynamoDB = [AWSDynamoDB defaultDynamoDB];
AWSDynamoDBUpdateItemInput *updateItemInput = [AWSDynamoDBUpdateItemInput new];

// テーブル名
updateItemInput.tableName = [Book dynamoDBTableName];

// ハッシュキーセット
NSString *hashKeyAttribute = [Book hashKeyAttribute];
AWSDynamoDBAttributeValue *hashKeyValue = [AWSDynamoDBAttributeValue new];
hashKeyValue.S = @"3456789012";
updateItemInput.key = @{hashKeyAttribute : hashKeyValue};

// 更新文
updateItemInput.updateExpression = @"set ViewCount = ViewCount + :cnt";
// 更新文で使う属性値のセット
AWSDynamoDBAttributeValue *expressionAttributeValue = [AWSDynamoDBAttributeValue new];
expressionAttributeValue.N = @"1";
updateItemInput.expressionAttributeValues = @{@":cnt" : expressionAttributeValue};
updateItemInput.returnValues = AWSDynamoDBReturnValueUpdatedNew;

// 更新実行
[[dynamoDB updateItem:updateItemInput]
 continueWithBlock:^id(BFTask *task) {
     if (task.error) {
         NSLog(@"The request failed. Error: [%@]", task.error);
     }
     if (task.exception) {
         NSLog(@"The request failed. Exception: [%@]", task.exception);
     }
     if (task.result) {
         //Do something with the result.
     }
     return nil;
}];

条件つき更新

UpdateItemの条件文(ConditionExpression)を使って行います。
以下の例では、ViewCountが5より大きい場合、Level属性に1をセットしています。

注意

条件式にマッチしない場合、エラーとなるようです。

公式ドキュメントによる条件つき更新の説明

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/WorkingWithItems.html#WorkingWithItems.AtomicCounters
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Expressions.Modifying.html

// ViewCountに+1する (アトミックカウンタ)
AWSDynamoDB *dynamoDB = [AWSDynamoDB defaultDynamoDB];
AWSDynamoDBUpdateItemInput *updateItemInput = [AWSDynamoDBUpdateItemInput new];

// テーブル名
updateItemInput.tableName = [Book dynamoDBTableName];

// ハッシュキーセット
NSString *hashKeyAttribute = [Book hashKeyAttribute];
AWSDynamoDBAttributeValue *hashKeyValue = [AWSDynamoDBAttributeValue new];
hashKeyValue.S = @"3456789012";
updateItemInput.key = @{hashKeyAttribute : hashKeyValue};

// 更新文
updateItemInput.updateExpression = @"set #lvl = :lvl";

// 条件文
updateItemInput.conditionExpression = @"ViewCount > :cnt_level";

// 更新文で使う属性名のセット
updateItemInput.expressionAttributeNames = @{@"#lvl": @"Level"};

// 更新文と条件文で使う属性値のセット
NSMutableDictionary *expAttributeValues = [[NSMutableDictionary alloc] init];
// :lvlのセット
AWSDynamoDBAttributeValue *updateExpAttributeValue = [AWSDynamoDBAttributeValue new];
updateExpAttributeValue.N = @"1";
expAttributeValues[@":lvl"] = updateExpAttributeValue;
// :cnt_levelのセット
AWSDynamoDBAttributeValue *conditionExpAttributeValue = [AWSDynamoDBAttributeValue new];
conditionExpAttributeValue.N = @"5";
expAttributeValues[@":cnt_level"] = conditionExpAttributeValue;

updateItemInput.expressionAttributeValues = expAttributeValues;

// 更新実行
[[dynamoDB updateItem:updateItemInput]
 continueWithBlock:^id(BFTask *task) {
     if (task.error) {

         // 条件文にマッチしない場合、エラーとなる
         if (task.error.code == AWSDynamoDBErrorConditionalCheckFailed) {
             DEBUG_LOG(@"%@", @"条件式にマッチせず");
         }

         NSLog(@"The request failed. Error: [%@]", task.error);
     }
     if (task.exception) {
         NSLog(@"The request failed. Exception: [%@]", task.exception);
     }
     if (task.result) {
         //Do something with the result.
     }
     return nil;
}];

BatchGetItem / BatchWriteItem

AWSDynamoDB *dynamoDB = [AWSDynamoDB defaultDynamoDB];

//Write Request 1
AWSDynamoDBAttributeValue *hashValue1 = [AWSDynamoDBAttributeValue new];
hashValue1.S = @"3210987654";
AWSDynamoDBAttributeValue *otherValue1 = [AWSDynamoDBAttributeValue new];
otherValue1.S = @"Some Title";

AWSDynamoDBWriteRequest *writeRequest = [AWSDynamoDBWriteRequest new];
writeRequest.putRequest = [AWSDynamoDBPutRequest new];
writeRequest.putRequest.item = @{
                                 @"ISBN" : hashValue1,
                                 @"Title" : otherValue1
                                 };

//Write Request 2
AWSDynamoDBAttributeValue *hashValue2 = [AWSDynamoDBAttributeValue new];
hashValue2.S = @"8901234567";
AWSDynamoDBAttributeValue *otherValue2 = [AWSDynamoDBAttributeValue new];
otherValue2.S = @"Another Title";

AWSDynamoDBWriteRequest *writeRequest2 = [AWSDynamoDBWriteRequest new];
writeRequest2.putRequest = [AWSDynamoDBPutRequest new];
writeRequest2.putRequest.item = @{
                                  @"ISBN" : hashValue2,
                                  @"Title" : otherValue2
                                  };

AWSDynamoDBBatchWriteItemInput *batchWriteItemInput = [AWSDynamoDBBatchWriteItemInput new];
batchWriteItemInput.requestItems = @{@"Books": @[writeRequest,writeRequest2]};

[[dynamoDB batchWriteItem:batchWriteItemInput]
 continueWithBlock:^id(BFTask *task) {
     if (task.error) {
         NSLog(@"The request failed. Error: [%@]", task.error);
     }
     if (task.exception) {
         NSLog(@"The request failed. Exception: [%@]", task.exception);
     }
     if (task.result) {
         //Do something with the result.
     }
     return nil;
 }];
23
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
23