AWS SDK for Javaを使う#Amazon DynamoDB

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

いまやクラウドサービスの代表格とも言えるAmazonですが、EC2やS3をはじめとして、さまざまなサービスを提供しています。
数年前に私も少しだけEC2やS3を使用したことがあるのですが、最近はあまりさわっていませんでした。
しかし今回AWSについての調査をきっかけに、各種AWSサービスについて復習&AWS SDKでの動作確認をしていきたいと思います。

今回はNoSQLの高速データベース、Amazon DynamoDBです。

今回使用した動作環境は以下のとおりです。

  • OS : MacOS X 10.7.2
  • Java : 1.6.0_26
  • Scala : 2.9.1 final
  • SBT : 0.11.2

なお、AWSへの登録は終わっているものとします。

Amazon DynamoDB

Amazon DynamoDBについての解説はここに詳しく書いてあるので詳細は省きます。
SimpleDBとかぶる部分もありますが、大量データ時のパフォーマンスや容量についてはDynamoのほうが優れているようです。
それではプログラムからDynamoDBにアクセスしてみましょう。

実行環境のセットアップ

以前と同じくsbt + scala + aws-sdk-javaをセットアップしておきましょう。

Amazon DynamoDBサンプル作成

まずは初期処理です。いつものようにキーを指定してAmazonDynamoDBClientオブジェクトを作成しています。

  // アクセスキー
  val accessKey = "アクセスキー"
  // シークレットキー
  val secretKey = "シークレットキー"
  val credentials = new BasicAWSCredentials(accessKey,secretKey)

  val dynamoDB = new AmazonDynamoDBClient(credentials)

次にテーブルを作成します。テーブルの作成はCreateTableRequestにテーブル名やキーの設定を行います。
ここではMyUserという名前のテーブルを、プライマリーキーはidという名前で作成しています。
createTableメソッドを呼べばテーブル作成のリクエストが送信されます。

  //テーブル定義
  val tableName = "MyUser"
  val createTableRequest = 
    new CreateTableRequest()
       .withTableName(tableName)
	.withKeySchema(new KeySchema(new KeySchemaElement()
	.withAttributeName("id")
	.withAttributeType("N")))
	.withProvisionedThroughput( new ProvisionedThroughput()
	.withReadCapacityUnits(10L)
	.withWriteCapacityUnits(10L))

	val createdTableDescription = dynamoDB.createTable(createTableRequest).getTableDescription()
	println("Created Table: " + createdTableDescription)

テーブルの作成ができたらテーブル情報を確認してみましょう。
describeTableメソッドを呼べば、テーブル情報を取得できます。

  val describeTableRequest = new DescribeTableRequest().withTableName(tableName) 
  val tableDescription = dynamoDB.describeTable(describeTableRequest).getTable()
  println("Table Description: " + tableDescription)

次はデータの登録です。HashMapを使用してputItemメソッドを呼べば簡単に登録することができます。

  //データの登録
  val item = Map("id" -> new AttributeValue().withN(Integer.toString(1)),
                 "name" -> new AttributeValue("taro"),
                 "age" -> new AttributeValue("20"))

  val putItemRequest = new PutItemRequest(tableName, item)
  val putItemResult = dynamoDB.putItem(putItemRequest)
  println("Result: " + putItemResult)

登録ができたら次は検索です。検索はScanRequestオブジェクトにテーブル名とフィルター用マップを渡します。
フィルター用マップにはConditionオブジェクトが設定されています。
Conditionオブジェクトは、条件とその値を設定し、マップのキーにフィールド名を指定しています。

  //IDが1のものを検索
  val condition = new Condition()
	.withComparisonOperator(ComparisonOperator.EQ.toString()) //=である
	.withAttributeValueList(new AttributeValue().withN(Integer.toString(1))) //数値の1である
  //検索条件のフィールドを指定
  val scanFilter = Map("id" -> condition)
  val scanRequest = new ScanRequest(tableName).withScanFilter(scanFilter)
  val scanResult = dynamoDB.scan(scanRequest)
  println("Scan Result: " + scanResult)

これで基本的な更新と検索ができるのですが、DynamoDBはDynamoDBMapperというクラスを提供しています。
これはモデルにアノテーションを付与することでORマッパーチックに使用できます。
では、MyUserモデルを作成して登録してみましょう。

  @DynamoDBTable(tableName = "MyUser")
  case class MyUser {
    var id:Int = _
    var name:String = _
  
    def setId(_id:Int) = id = _id

    def setName(_name:String) = name = _name
  
    @DynamoDBHashKey(attributeName = "id")
    def getId = id

    @DynamoDBAttribute(attributeName = "name")
    def getName = name
  }

  //DMMapperの作成        
  val dynamoMapper = new DynamoDBMapper(dynamoDB)  

  //DataMapperの登録
  val user = new MyUser()
  user.setId(100)
  user.setName("m-user")
  dynamoMapper.save(user)

DynamoDBTableアノテーションを使用してテーブルとクラスを関連付け、プロパティのgetterにもキーや属性のアノテーションを指定します。
saveメソッドにオブジェクトを渡せば、登録することができます。
ただ、このサンプルはScalaを使用しているので、モデルの記述がかなり冗長になっています。

次にDBMapperを使用した検索です。検索はloadメソッドを使用し、マップするオブジェクトとキーを渡して検索しています。

  //DataMapperでidが100のものを検索
  val u = dynamoMapper.load(classOf[MyUser], "100")
  println("mapper id=: " + u.getId)
  println("mapper name=: " + u.getName)

最後にサンプル全文をのせておきます。一度目の実行でテーブルを作成し、2度目の実行でデータ登録と検索を行います。
※1度目の実行後、しばらく時間がたたないと、テーブルができていないのでデータ登録が失敗する可能性があります

import com.amazonaws.auth.BasicAWSCredentials

import com.amazonaws.auth.PropertiesCredentials
import com.amazonaws.services.dynamodb.AmazonDynamoDBClient
import com.amazonaws.services.dynamodb.model._
import com.amazonaws.services.dynamodb.datamodeling._
import com.amazonaws.AmazonServiceException

import scala.collection.JavaConversions._
import scala.reflect._

object DynamoMain extends App{

  /**
   * 指定した名前のテーブルがあるかどうか
   */
  def isTable(tableName:String) :Boolean = {
	try{
	  val request = new DescribeTableRequest().withTableName(tableName)
      val tableDescription = dynamoDB.describeTable(request).getTable()
      val tableStatus = tableDescription.getTableStatus()
	  println("tableStatus =" + tableStatus)
	  true
	}catch {
	  case e:AmazonServiceException => println(e.toString);false
	}
  }

  // アクセスキー
  val accessKey = "アクセスキー"
  // シークレットキー
  val secretKey = "シークレットキー"

  val credentials = new BasicAWSCredentials(accessKey,secretKey)

  val dynamoDB = new AmazonDynamoDBClient(credentials)
  val dynamoMapper = new DynamoDBMapper(dynamoDB)  

  //テーブル定義
  val tableName = "MyUser"

  if(isTable(tableName)) {
	println("テーブルが存在するのでデータの登録と検索をします")
	val describeTableRequest = new DescribeTableRequest().withTableName(tableName)
	val tableDescription = dynamoDB.describeTable(describeTableRequest).getTable()
	println("Table Description: " + tableDescription)
	

    //通常の登録
    val item = Map("id" -> new AttributeValue().withN(Integer.toString(1)),
				   "name" -> new AttributeValue("tarosss"),
				   "age" -> new AttributeValue("20"))

    val putItemRequest = new PutItemRequest(tableName, item)
    val putItemResult = dynamoDB.putItem(putItemRequest)
    println("Result: " + putItemResult)

    //DataMapperの登録
	val user = new MyUser()
	user.setId(100)
	user.setName("m-user")
	dynamoMapper.save(user)


	//IDが1のものを検索
    val condition = new Condition()
	.withComparisonOperator(ComparisonOperator.EQ.toString())
	.withAttributeValueList(new AttributeValue().withN(Integer.toString(1)))

    val scanFilter = Map("id" -> condition)
    val scanRequest = new ScanRequest(tableName).withScanFilter(scanFilter)
    val scanResult = dynamoDB.scan(scanRequest)
    println("Scan Result: " + scanResult)

    //DataMapperでidが100のものを検索
    val u = dynamoMapper.load(classOf[MyUser], "100")
    println("mapper id=: " + u.getId)
    println("mapper name=: " + u.getName)

  } else {
	println("テーブルがないので作成します")
	val createTableRequest = new CreateTableRequest().withTableName(tableName)
	.withKeySchema(new KeySchema(new KeySchemaElement().withAttributeName("id").withAttributeType("N")))
	.withProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits(10L).withWriteCapacityUnits(10L))

	val createdTableDescription = dynamoDB.createTable(createTableRequest).getTableDescription()
	println("Created Table: " + createdTableDescription)
  }



}

@DynamoDBTable(tableName = "MyUser")
case class MyUser {
  var id:Int = _
  var name:String = _
  
  def setId(_id:Int) = id = _id

  def setName(_name:String) = name = _name
  
  @DynamoDBHashKey(attributeName = "id")
  def getId = id

  @DynamoDBAttribute(attributeName = "name")
  def getName = name
}

実行するにはいつものようにsbtを起動してrunコマンドを実行してください。

% sbt
> run

まとめ

今回はDynamoDBを使用してみました。
まだベータですが、simpelDBに代わる高速なデータベースということで、今後も使用する機会は多いと思います。

参考サイトなど