FURYU Tech Blog - フリュー株式会社

フリュー株式会社の開発者が技術情報を発信するブログです。

AWS 1ヶ月導入記 part 6 SimpleJPAを用いたプログラミング

みなさん、こんにちは!フリューでモバイルサイト開発を行っている鷲見といいます。

現在、あるソーシャルプラットフォーム上のアプリケーションを構築するプロジェクトでの、クラウド環境の導入の進行状況をそのまま記事にしています。

前回はAWS SDK for Javaを用いてSimpleDBを操作するプログラムについて解説しました。

前回から『Amazon Web Services 1ヶ月導入記』を『AWS 1ヶ月導入記』と省略表記にしています。

さて、今回はSimpleJPAを使って、プログラムからSimpleDBへの接続を行うを方法を解説したいとおもいます。


SimpleJPAとは?

SimpleJPAとはオープンソースAmazon SimpleDB向けのJava Persistence API(JPA)実装です。つまりはSimpleDB向けのORM(Object-relational mapping)フレームワークです。SimpleDBのドメインから取得したアイテム情報をJavaオブジェクトに変換して操作することができます。

SimpleJPA

The Java Persistence API – A Simpler Programming Model for Entity Persistence

特徴

SimpleJPAには以下のような特徴があります。

  • ManyToOneの関連での遅延ローディングのサポート
  • OneToManyの関連での遅延ローディングのサポート
  • Amazon S3を利用したラージオブジェクト(LOB)のサポート
  • 二次キャッシュ
  • 結果セットの並行参照
  • JPA Queriesのサブセットを利用可能

依存するライブラリ

SimpleJPAは以下のライブラリに依存しています。

  • commons-lang
  • commons-collections
  • commons-logging
  • commons-codec
  • cglib-nodep
  • kitty-cache
  • ehcache
  • scannotation
  • javassist
  • ejb3-persistence-api
  • AWS SDK for Java
  • Apache HttpClient
  • AMS

上記の依存ライブラリにAWS SDK for Javaがあることからも分かるように、内部的にAWS SDK for Javaを利用しています。

SimpleJPAの制限

SimpleJPAAWSのサンプルプログラムでも利用されるフレームワークですので、非常に便利なのですが、その便利さの反面制限もあります。

私が利用して気づいた制限は以下のとおりです。

  • プロキシ経由のアクセスを行うことが出来ない
  • ドメイン名に接頭辞を付けなければならない(付けないという設定がない)
  • アイテム名と同じ値を持つ属性が必要になる
  • 複数値を扱うことができない(putはできるがget,selectできない)
  • BatchPutAttributes,BatchDeleteAttributesなどのバッチ処理APIが利用できない

このような制限で問題が発生する場合には、SimpleJPAでなく、AWS SDK for Javaで実装することをおすすめします。

また、ドキュメントが少なく、日本語の資料はほとんど無いというデメリットも挙げられます。

ちなみに弊社プロジェクトではSimpleJPAの利用も検討しましたが、上記のような制限が多く、自由度が低かったという点で利用は見送りました。


サンプルプログラム

前回と同じく、東京リージョンで以下のようなドメインを作り、データを投入し、情報を取得するといったサンプルプログラムです。

Employee

アイテム名 name pref unit manager age
Emp1 鷲見 京都 企画 31
Emp2 九岡 福井 開発 佐藤 26
Emp3 川下 宮崎 企画 鈴木 27

Employee.java(Employeeドメインを表すJavaクラス)

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Employee {

    @Id
    private String id;
    private String name;
    private String pref;
    private String unit;
    private Integer age;
    private String manager;

    @Id
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPref() {
        return pref;
    }

    public void setPref(String pref) {
        this.pref = pref;
    }

    public String getManager() {
        return manager;
    }

    public void setManager(String manager) {
        this.manager = manager;
    }

    public String getUnit() {
        return unit;
    }

    public void setUnit(String unit) {
        this.unit = unit;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

EmployeeTest.java(Employeeドメインを操作するJavaクラス)

iimport java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Query;

import com.spaceprogram.simplejpa.EntityManagerFactoryImpl;

public class EmployeeTest {

    public static void main(String[] args){

        // リージョンの設定
        Map properties = new HashMap();
        properties.put("sdbEndpoint", "sdb.ap-northeast-1.amazonaws.com");
        EntityManagerFactory factory = new EntityManagerFactoryImpl("TEST",properties);
        EntityManager entityManager = factory.createEntityManager();
        try{

            //データの登録
            Employee employee = new Employee();
            employee.setId("Emp1");
            employee.setName("鷲見");
            employee.setPref("京都");
            employee.setUnit("開発");
            employee.setAge(31);

            entityManager.persist(employee);

            employee = new Employee();
            employee.setId("Emp2");
            employee.setName("九岡");
            employee.setPref("福井");
            employee.setManager("佐藤");
            employee.setUnit("開発");
            employee.setAge(26);

            entityManager.persist(employee);

            employee = new Employee();
            employee.setId("Emp3");
            employee.setName("川下");
            employee.setPref("宮崎");
            employee.setManager("鈴木");
            employee.setUnit("企画");
            employee.setAge(27);

            entityManager.persist(employee);

            // 情報の取得
            Employee findResult = entityManager.find(Employee.class, "Emp1");
            System.out.print(findResult.getId()+"[");
            System.out.print(" name:"+findResult.getName());
            System.out.print(" pref:"+findResult.getPref());
            System.out.print(" unit:"+findResult.getUnit());
            System.out.print(" manager:"+findResult.getManager());
            System.out.print(" age:"+findResult.getAge());
            System.out.println("]");

            // 情報の取得2
            Query query = entityManager.createQuery("select * from Employee where unit=:unit");
            query.setParameter("unit", "開発");
            List employees = query.getResultList();
            for(Employee result:employees){
                System.out.print(result.getId()+"[");
                System.out.print(" name:"+result.getName());
                System.out.print(" pref:"+result.getPref());
                System.out.print(" unit:"+result.getUnit());
                System.out.print(" manager:"+result.getManager());
                System.out.print(" age:"+result.getAge());
                System.out.println("]");
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally{
            entityManager.close();
            factory.close();
        }
    }
}

リージョン設定とEntityManagerの生成

Map properties = new HashMap();
properties.put("sdbEndpoint", "sdb.ap-northeast-1.amazonaws.com");
EntityManagerFactory factory = new EntityManagerFactoryImpl("TEST",properties);
EntityManager entityManager = factory.createEntityManager();

クライアントはデフォルトのままではus-eastリージョンに接続するようになっているので、東京リージョンに設定するようにリージョンの設定を行います。

SimpleJPAではEntityManagerを使って、データの操作を行います。リージョン情報などの情報を設定したMapを使ってEntityManagerのFactoryを生成し、FactoryからEntityManagerを生成します。

EntityManagerFactoryImpl生成時に渡している”TEST”という文字列は、ドメイン名の接頭辞に使われます。

アカウント設定

SimpleJPAは内部的にAWS SDK for Javaを利用しているため、アカウント情報はAWSCredentials.propertiesから読み込みます。

AWS Credentials.propertiesファイルの内容は以下のようになっています。

secretKey={アカウントのSecret Access Key}
accessKey={アカウントのAccess Key ID}

AWS Credentials.propertiesファイルをパス上に生成しておくことで、アカウント情報を自動的に設定してくれます。

データの投入

Employee employee = new Employee();
employee.setId("Emp1");
employee.setName("鷲見");
employee.setPref("京都");
employee.setUnit("開発");
employee.setAge(31);

entityManager.persist(employee);

AWS SDK for Javaではデータの投入前にドメインの作成を行なっていましたが、SimpleJPAではデータの投入時にドメインが存在しない場合は、自動的にドメインを生成します。Factory生成時に”TEST”という接頭辞を渡しているため、データ投入実行時に『TEST-Employee』というドメインが生成されます。ちなみにこの接頭辞は省略することはできません。空文字を渡した場合『-Employee』というドメイン名になってしまいます。

データの投入はEmployeeドメインを表すJavaオブジェクトを生成、値の設定を行い、EntityManagerのpersistメソッドに渡すだけです。

なお、ドメインを表すクラスには@Entityアノテーションを付ける必要があります。また、ドメインを表すクラスには必ず@Idアノテーションが付いたフィールドが必要です。@Idアノテーションが付いた項目の値がそのままアイテム名となります。サンプルプログラムでは@Idがついたidフィールドの値がアイテム名となります。(上の例では『Emp1』がアイテム名となります。)

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Employee {

    @Id
    private String id;
    …

    @Id
    public String getId() {
        return id;
    }
        …
}

データの取得1

Employee findResult = entityManager.find(Employee.class, "Emp1");

SimpleDBのGetAttributes APIに相当するのがEntityManagerのfindメソッドを利用します。findメソッドにはドメインを表すクラスとアイテム名を引数で渡して、該当するドメインのアイテムを取得します。

データの取得2

Query query = entityManager.createQuery("select * from Employee where unit=:unit");
query.setParameter("unit", "開発");
List employees = query.getResultList();

SimpleDBのSelect APIを呼び出すためにはJPAQueryを利用します。EntityManagerのcreateQueryメソッドにSelect APIに渡すのと同等のクエリを渡します。『:unit』となっているのはプレースホルダーで、QueryのsetParameterで渡した値に置き換えてくれます。

SimpleDB上ではドメイン名は『TEST-Employee』となっていますが、createQueryメソッドに渡すクエリ内では『Employee』で問題ありません。内部でSimpleJPAが『TEST-Employee』に変換してくれます。

後処理

}finally{
    entityManager.close();
    factory.close();
}

SimpleJPAではEntityManagerFactoryとEntityManagerは必ずcloseメソッドを呼び出す必要があります。

実行結果

上記のサンプルプログラムを実行した結果は以下のとおりになります。

Emp1[ name:鷲見 pref:京都 unit:開発 manager:null age:31]
Emp1[ name:鷲見 pref:京都 unit:開発 manager:null age:31]
Emp2[ name:九岡 pref:福井 unit:開発 manager:佐藤 age:26]

数値型の扱い

サンプルプログラムではageフィールドが数値型となっています。この値がSimpleDB上では『9223372036854775808 + フィールドの値』を文字列にした値が保存されています。上記Emp1の場合は『09223372036854775839』(9223372036854775808+31)となっています。

これは以前に『Amazon Web Services 1ヶ月導入記 part 4 SimpleDBの制限を回避する』で紹介した基準値を決めて数値型を扱う方法で、『9223372036854775808』を基準値としています。これにより64ビットの符号付き整数を扱うことができます。『9223372036854775808』が基準値となっているのは64ビットの符号付き整数の範囲が-9223372036854775808 – 9223372036854775807となっているからだと思われます。

もちろん、SimpleDBからデータを取得したときにはSimpleJPAが内部で通常の数値型に変換してくれます。

なお、数値型のデータを実際に見ると24桁の数値なうえ、計算しないと元の値がわからないので、データを把握しづらいので注意が必要です。


まとめ

今回はSimpleJPAを用いてSimpleDBを操作するプログラムを解説しました。

SimpleJPAでのSimpleDBプログラミングでは

  • EntityManagerからドメインを表すオブジェクトを通じてドメインの操作を行う
  • ドメインは存在しない場合は自動的にドメインが生成される
  • ドメイン名には接頭辞が付く
  • @Idがついているフィールドの値がアイテム名となる
  • 数値型はSimpleDBでは『9223372036854775808』を基準値とした文字列として扱われる。

ということを確認しました。

SimpleJPAをつかえばSimpleDBから簡単にJavaオブジェクトへのマッピングを行うことができます。ただし、制限も強く自由度が低くなってしまうので、利用を検討する際には、制限やデメリットを確認して、要件にマッチするかを確認することが必要です。

さて、次回以降はSimpleDBから離れ、AWSでのデータバックアップなどについてお話ししたいと思います。