Hector で JPA の Entity オブジェクトを Cassandra に登録
Java 用 Cassandra クライアント Hector の Object Mapper を使って JPA の Entity オブジェクトを Cassandra に保存する方法をご紹介します。
ソースは http://github.com/fits/try_samples/tree/master/blog/20120311/
事前準備
Cassandra サーバーと CLI を起動して、今回使用するキースペースとカラムファミリを作成しておきます。
キースペース Sample とカラムファミリ Order を作成
> cassandra-cli.bat Starting Cassandra Client Welcome to Cassandra CLI version 1.0.8 ・・・ [default@unknown] connect localhost/9160; Connected to: "Test Cluster" on localhost/9160 [default@unknown] create keyspace Sample; ・・・ [default@unknown] use Sample; Authenticated to keyspace: Sample [default@Sample] create column family Order with comparator = UTF8Type; ・・・
なお、Cassandra 1.0.8 の Windows 用実行スクリプト(cassandra.bat や cassandra-cli.bat)は環境変数 CASSANDRA_CONF で設定ファイルの場所を指定できるようになっていなかったので*1、以下のように変更して使いました。
cassandra.bat の変更箇所
・・・ REM Ensure that any user defined CLASSPATH variables are not used on startup if NOT DEFINED CASSANDRA_CONF set CASSANDRA_CONF="%CASSANDRA_HOME%\conf" set CLASSPATH=%CASSANDRA_CONF% ・・・
Hector のサンプル
まず、通常の Hector を使ったデータ登録の Groovy サンプルです。commons-pool を @GrabExclude で指定する必要がありました。
hector_sample.groovy
package fits.sample @Grapes([ @Grab('me.prettyprint:hector-core:1.0-3'), @GrabExclude('commons-pool#commons-pool') ]) @Grab('org.slf4j:slf4j-jdk14:1.6.4') import me.prettyprint.hector.api.factory.HFactory import me.prettyprint.cassandra.serializers.StringSerializer def cluster = HFactory.getOrCreateCluster("Test Cluster", "localhost:9160") def keyspace = HFactory.createKeyspace('Sample', cluster) def mutator = HFactory.createMutator(keyspace, StringSerializer.get()) mutator.addInsertion("key1", "Order", HFactory.createStringColumn("user_id", "U2")) def res = mutator.execute() println res cluster.getConnectionManager().shutdown()
CLI で確認した登録内容は以下の通りです。
CLI での確認結果
[default@Sample] get Order[utf8('key1')]; => (column=user_id, value=5532, timestamp=1331465077828000) Returned 1 results. ・・・
Hector Object Mapper のサンプル(Groovy版)
それでは本題の JPA の Entity アノテーションを付与したオブジェクトを登録するサンプルです。
まずは Groovy で実装してみます。
通常は、EntityManagerImpl コンストラクタの第2引数で指定したパッケージ名から永続対象クラスを自動スキャンしてくれるのですが、今回は同一スクリプト内で永続クラスの Order を定義しており、このままでは永続対象とならないので ClassCacheMgr を使って対応しました。
JPA の @Embedded や @OneToMany は今のところサポートされていないみたいで、コレクション (今回のサンプルでは List) の内容を保存・復元するには me.prettyprint.hom.annotations.Column アノテーションを使う必要がありました。
また、EntityManagerImpl は今のところ javax.persistence.EntityManager インターフェースを実装しているわけでは無く、persist() と find() メソッド程度しか使えません。
ちなみに、@Id アノテーションを付与したプロパティの値がキーとして登録されます。
hector_om_sample.groovy
package fits.sample @Grapes([ @Grab('me.prettyprint:hector-object-mapper:3.0-02'), @GrabExclude('commons-pool#commons-pool') ]) @Grab('org.slf4j:slf4j-jdk14:1.6.4') import javax.persistence.* import me.prettyprint.hector.api.factory.HFactory import me.prettyprint.hom.EntityManagerImpl import me.prettyprint.hom.ClassCacheMgr @Entity @Table(name = "Order") class Order { @Id String id @Column(name = "user_id") String userId @me.prettyprint.hom.annotations.Column(name = "lines") List<OrderLine> lines } class OrderLine implements Serializable { String productId int quantity } def cluster = HFactory.getOrCreateCluster("Test Cluster", "localhost:9160") def keyspace = HFactory.createKeyspace('Sample', cluster) //Order クラスを永続対象にするための設定(自動スキャンされないため) def cacheMgr = new ClassCacheMgr() cacheMgr.initializeCacheForClass(Order) def em = new EntityManagerImpl(keyspace, null, cacheMgr, null) def data = new Order(id: "id1", userId: "U1", lines: [ new OrderLine(productId: "P1", quantity: 1), new OrderLine(productId: "P2", quantity: 2) ]) //保存 em.persist(data) //取得 def res = em.find(Order, "id1") println res.dump() res.lines.each { println "${it.productId}, ${it.quantity}" }
実行結果
> groovy hector_om_sample.groovy ・・・ <fits.sample.Order@140bd470 id=id1 userId=U1 lines=[fits.sample.OrderLine@4a7d5381, fits.sample.OrderLine@69f5605b]> P1, 1 P2, 2
CLI で確認した結果は以下の通り。lines の部分が複数カラムに分かれている点に注目です。
CLI での確認結果
[default@Sample] get Order[utf8('id1')]; => (column=lines, value=6a6176612e7574696c2e41727261794c6973743a32, timestamp=1331466085998000) => (column=lines:0, value=aced000573720015666974732e73616d706c652e4f726465724c696e6511ad8a3d5308252a0200024900087175616e746974794c000970726f6475637449647400124c6a6176612f6c616e672f537472696e673b7870000000017400025031, timestamp=1331466086016000) => (column=lines:1, value=aced000573720015666974732e73616d706c652e4f726465724c696e6511ad8a3d5308252a0200024900087175616e746974794c000970726f6475637449647400124c6a6176612f6c616e672f537472696e673b7870000000027400025032, timestamp=1331466086016001) => (column=user_id, value=5531, timestamp=1331466085993000) Returned 4 results. ・・・
Hector Object Mapper のサンプル(Java版)
先ほどのサンプルを Java で実装した版は以下の通りです。
fits.sample パッケージを自動スキャンして Order を永続対象とするので ClassCacheMgr は不要です。
Order.java
package fits.sample; import java.util.List; import javax.persistence.*; @Entity @Table(name = "Order") public class Order { @Id private String id; @Column(name = "user_id") private String userId; @me.prettyprint.hom.annotations.Column(name = "lines") private List<OrderLine> lines; ・・・ 各種アクセサメソッド ・・・ }
Main.java
package fits.sample; import java.util.ArrayList; import me.prettyprint.hector.api.Cluster; import me.prettyprint.hector.api.Keyspace; import me.prettyprint.hector.api.factory.HFactory; import me.prettyprint.hom.EntityManagerImpl; public class Main { public static void main(String[] args) { Cluster cluster = HFactory.getOrCreateCluster("Test Cluster", "localhost:9160"); Keyspace keyspace = HFactory.createKeyspace("Sample", cluster); EntityManagerImpl em = new EntityManagerImpl(keyspace, "fits.sample"); Order data = new Order(); data.setId("jid1"); data.setUserId("U1"); data.setLines(new ArrayList<OrderLine>()); OrderLine line1 = new OrderLine(); line1.productId = "P1"; line1.quantity = 1; data.getLines().add(line1); ・・・ //保存 em.persist(data); Order res = em.find(Order.class, "jid1"); System.out.printf("%s - %s\n", res.getId(), res.getUserId()); for (OrderLine line : res.getLines()) { System.out.printf("%s, %d\n", line.productId, line.quantity); } } }
pom.xml
<project ・・・> ・・・ <dependencies> <dependency> <groupId>me.prettyprint</groupId> <artifactId>hector-object-mapper</artifactId> <version>3.0-02</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jdk14</artifactId> <version>1.6.4</version> </dependency> </dependencies> ・・・ </project>
実行結果
> mvn compile exec:java ・・・ jid1 - U1 P1, 1 P2, 2 ・・・
*1:sh 用の cassandra は CASSANDRA_CONF で指定できるようになっている