はじめに
これはJava Advent Calendar 2016の11日目の記事です。
ことの発端は、Gradleスキーな私がGradleでGoogle Cloud SQLをGoogle Container Engineで使いたかったところにあります。
そのうえで、いくつかの壁にぶち当たりましたので、その一部を忘備録の意味も込めて、少し書いてみることにしました。
Google Cloud SQLとは、Google Cloud Platfromの一機能であり、MySQL互換のDBです。
Google Container Engineも、Google Cloud Platformの一機能で、簡単に言えばDockerホストです。
多々語弊がありますので、この辺りは特に専門家・専門書・専門ページを参考にしてください。
JDOとDataNucleusとは
JDOを簡単に説明すると、JavaのクラスをそのままDBに格納できる仕組みです。
JDOを用いて、クラスの状態を保持することを永続化などと表現したりするそうです。
この記事ではその構造に関しては置いとくこととして、まず利用できるようにしていきたいと思います。
DataNucleusはJPAおよびJDOの実装で、Google AppEngineでもよく使用されています。
そのため、DataNucleusに関してWeb検索すると、AppEngineに関する記事が多くヒットするため、少し戸惑いやすいこともあります。
公式ページはこちらです。
http://www.datanucleus.org/
この記事では、最新一歩手前のDataNucleus 5.0.3を紹介したいと思います。
Gradleプロジェクト
では、さっそくGradleプロジェクトを作成したいと思います。
説明は苦手なので、コードで語りたいと思います。
buildscript {
repositories {
jcenter()
}
}
apply plugin: 'java'
apply plugin: 'application'
mainClassName = 'net.leak4mk0.test.datanucleus.Main'
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
repositories {
jcenter()
}
dependencies {
// JDO
compile 'org.datanucleus:datanucleus-core:5.0.3'
compile 'org.datanucleus:datanucleus-api-jdo:5.0.3'
compile 'org.datanucleus:datanucleus-rdbms:5.0.3'
compile 'org.datanucleus:javax.jdo:3.2.0-m5'
// DB
compile 'com.h2database:h2:1.4.193'
}
注意すべき点は、JDOのインタフェースとしてjavax.jdo:jdo-api:*
に依存するのではなく、DataNucleusから提供されているorg.datanucleus:javax.jdo:*
を使用する点です。
DataNucleusの以前のバージョンでは問題がないようですが、5.x.x系ではDataNucleusのインターフェースを使うのが良いようです。
また、当然ですがDBコネクタが必要です。
今回は、データベースにH2を採用してみるので、H2のドライバーを依存関係に追加します。
ドライバーを使用するだけであれば、runtime
でいいのですが、今回はWebコンソールを使用したいので、compile
指定を行っています。
作ったGradleファイルをIntellij IDEAでインポートします。
Eclipse使いの方や、IDEは要らない派の方は、適切な方法で進めてください。(投げやり感)
余談ですが、Gradleももうv3.1なんですね…。IDEA様の右下を見ていて気付きました。
JDOモデル
続いて、JDOで使用するモデルクラスを作りたいと思います。
例を示しますが、このあたりはJDOに関する他のページ(特にAppEngine公式ドキュメント)を見ていただいたほうが参考になると思います。
よくある、Author
とBook
クラスです。
package net.leak4mk0.test.datanucleus;
import java.util.Set;
import javax.jdo.annotations.Element;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
@PersistenceCapable // 永続化するクラス
public class Author {
@PrimaryKey // 主キー
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) // 一意のキーを自動生成
private Long id;
@Persistent // 永続化するフィールド
private String name;
@Persistent(mappedBy = "author")
@Element(dependent = "true")
private Set<Book> books;
public Author(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
package net.leak4mk0.test.datanucleus;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
@PersistenceCapable
public class Book {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long id;
@Persistent
private Author author;
@Persistent
private String title;
public Book(Author author, String title) {
this.author = author;
this.title = title;
}
public Author getAuthor() {
return this.author;
}
public void setAuthor(Author author) {
this.author = author;
}
public String getTitle() {
return this.title;
}
public void setTitle(String title) {
this.title = title;
}
}
アノテーションを使用して、どのように永続化するかを指定していきます。
JDO設定ファイル
JDOがDBにアクセスするために、いくつかの設定が必要です。
JDO実装に何を利用するか、DBの接続先、ドライバー、ユーザーなどを設定していきます。
詳細は、以下のページを参考にしてください。
http://www.datanucleus.org/products/datanucleus/jdo/pmf.html
<?xml version="1.0" encoding="utf-8"?>
<jdoconfig xmlns="http://xmlns.jcp.org/xml/ns/jdo/jdoconfig">
<persistence-manager-factory name="memory-h2">
<property name="javax.jdo.PersistenceManagerFactoryClass"
value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>
<property name="javax.jdo.option.ConnectionURL" value="jdbc:h2:mem:test"/>
<property name="javax.jdo.option.ConnectionDriverName" value="org.h2.Driver"/>
<property name="javax.jdo.option.ConnectionUserName" value="sa"/>
<property name="javax.jdo.option.ConnectionPassword" value=""/>
<property name="datanucleus.schema.autoCreateTable" value="true"/>
</persistence-manager-factory>
</jdoconfig>
開発時と運用時で使用するDBを変えるなど、複数の設定を記述することも可能です。
persistence-manager-factory
要素をname
属性を変えて複数記述することにより行えます。
メインコード
そしてメインコードです。
実際のコード中でマルチバイトの変数名なんて使用しないかと思いますが、サンプルコードを書くときには便利かもしれません。
コード補完効きにくいですが…。
package net.leak4mk0.test.datanucleus;
import java.sql.SQLException;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import org.h2.tools.Server;
public class Main {
public static void main(String[] args) throws SQLException {
final PersistenceManagerFactory pmf = JDOHelper.getPersistenceManagerFactory("memory-h2");
final PersistenceManager pm = pmf.getPersistenceManager();
final Author 芥川龍之介 = new Author("芥川 龍之介");
final Author 福澤諭吉 = new Author("福澤 諭吉");
final Author 太宰治 = new Author("太宰 治");
pm.makePersistentAll(福澤諭吉, 太宰治); // 永続化
final Book 羅生門 = new Book(芥川龍之介, "羅生門");
pm.makePersistentAll(羅生門); // 永続化
final Book 西洋事情 = new Book(福澤諭吉, "西洋事情");
final Book 学問のすゝめ = new Book(福澤諭吉, "学問のすゝめ");
pm.makePersistentAll(西洋事情, 学問のすゝめ); // 永続化
final Book 富嶽百景 = new Book(太宰治, "富嶽百景");
final Book 走れメロス = new Book(太宰治, "走れメロス");
final Book 津軽 = new Book(太宰治, "津軽");
pm.makePersistentAll(富嶽百景, 走れメロス, 津軽); // 永続化
pm.deletePersistentAll(羅生門); // 削除
学問のすゝめ.author = 太宰治; // 変更
Server.main(); // コンソールの起動
}
}
基本的には、データを永続化するためにmakePersistent
、削除するためにdeletePersistent
します。
また、一度永続化したデータの変更したい場合には、フィールドを変更するだけでOKです。
今回は載せていませんが、検索する場合には、newQuery
やexecuteList
メソッドなどを使っていきます。
あと一歩、Enhance
最後に、一仕事残っています。
JDOを使って、モデルを永続化するのは非常に簡潔なコードで済むことがお分かりかと思いますが、それはDataNucleusが実際に必要なコードを各クラスに追加してくれているからです。
アノテーションの情報を参照して、コードを追加することをEnhanceと呼び、モデルクラスの永続化のためにはコンパイル後にEnhancerがモデルクラスをEnhanceしていく必要があります。
Enhancerの動かし方については以下のページに詳細が記されています。
http://www.datanucleus.org/products/datanucleus/jdo/enhancer.html
これをGradleに記述することが、この記事の一番のポイントです。(長かった…)
Antでの記述方法を参考にサクッと書いてみます。
// 前略...
task enhance {
description = 'DataNucleus enhancement'
dependsOn compileJava
doLast {
ant.taskdef(
name: 'datanucleusenhancer',
classpath: sourceSets.main.runtimeClasspath.asPath,
classname: 'org.datanucleus.enhancer.EnhancerTask'
)
ant.datanucleusenhancer(
classpath: sourceSets.main.runtimeClasspath.asPath,
failonerror: true,
verbose: true,
api: 'JDO') {
fileset(dir: sourceSets.main.output.classesDir)
}
}
}
classes.dependsOn enhance
classファイルを用いるため、compileJava
タスクより後に実行する必要があり、dependsOn
でcompileJava
に依存するように指定しています。
また、実行前に必ずEnhanceするように、classes
タスクをenhance
タスクに依存させています。
実行結果
あとは、gradlew run
すればブラウザが起動して、データベースの中身を確認できるはずです。
JDBC URLにjdbc:h2:mem:test
を指定するのを間違えないでください。
メインコードで行った永続化処理がすべて適用されていることが確認できると思います。
さいごに
今回は、GradleでDataNucleusのEnhance処理をどうすればよいのかが、一番の肝でした。
実際にGCP上で動作させるにあたり、Gradleに依存関係を追加していくと、ライブラリの競合などにより、さらに嵌りポイントがありました…。
明らかにコーディングに誤りがないのに例外が出る場合などには、一度Gradleの依存ツリーを見るとよいかもしれません。
最後までお読みいただき、ありがとうございました。
この記事で示したコードはご自由に使っていただいて大丈夫です。ただし、一切の責任を取りかねますのでご了承ください。