もっとも簡単な hibernate のサンプル

はじめに

ある程度実用的な Java ウエブアプリを作る場合、別のクラスのインスタンスをコレクションで含むエンティティを hibernate で操作する、みたいなことをよくやる。
が、意外にこのためのサンプルが少ない。

簡単なサンプルを作成したので、ちと解説。

モデルの準備

最終的に操作したい親のクラスを Person とした。
この次に Phone クラスを登場させるが、Person と Phone の関係は1対多(OneToMany)で一方向のみ。
hibernate がどのように動作するのか確認したいなら、これで十分でしょう。


@Entity(name = "person")
public class Person implements Serializable {

    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    
    @OneToMany(cascade = CascadeType.ALL)
    private List<phone> phones = new ArrayList<>();

    (以下、ゲッター・セッター)

ただし、フィールド変数に他のクラス(小クラス phone)のリスト phones を含めた。
ここを String とかにすると実用性ないので。

で、phone はこんな感じ。


@Entity(name = "phone")
public class Phone implements Serializable {

    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    
    private String number;
    (以下、セッター・ゲッター)

1対多の関係ならば、このクラスに Person の情報はいらないので、これでいい。
mapped by とかそういうややこしいのは要らない。忘れましょう。

ここまででも、ビルドして適当なアプリケーションサーバにデプロイすると(hibernate が適切に設定されているならば)指定したデータベースにテーブルを作ってくれる。

結合テーブル person_phone ができるが、このテーブルで person と phone を関連づけているわけですね。
見た目はスッキリしていないが、確実に動くプログラムを作成するのと hibernate がどの情報を用いて動作するのか把握するのは初学者にとって大事なのでそちらを優先。
いきなり難しいことをやる必要はない。

CRUD 操作

ここまでできたら、CRUD 操作用の REST api を作成。

まず、アプリケーションパスを設定(IDEが勝手に作ってくれると思うけど)。


@ApplicationPath("resources")
public class JAXRSConfiguration extends Application {
    
}

GET

次に、GET 命令で適当なオブジェクトを永続化させてみよう。


@Path("/personphone")
public class TestPersonPhone {
    
    @PersistenceContext(unitName="適当な名前")
    private EntityManager em;
    
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Transactional
    public String testPersist() {
        
        Person person = new Person();
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        List<Phone> phones = new ArrayList<>();
        
        phone1.setNumber("123");
        phone2.setNumber("456");
        phones.add(phone1);
        phones.add(phone2);
        person.setPhones(phones);
        
        em.persist(person);
        
        return "person phone persist";
    } 
}

デプロイ後、ブラウザで

http://localhost:8080/(プログラム名)/resources/personphone

にアクセスするとブラウザ上では person phone persist が返ってくる。

一方、データベースを覗くと phone テーブルのカラム number に “123”, “456” が入っているはずだ。

POST

これだけだと面白くないので、POST での操作も試してみよう。
上記のプログラムに以下のコードを追加。


    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Transactional
    public String testPersist2(String json) throws JsonProcessingException {
        
        ObjectMapper mapper = new ObjectMapper();
        Person person = new Person();
        
        person = mapper.readValue(json, new TypeReference<>() {});
        
        em.persist(person);
        
        return "person phone persist post";
    }

デプロイ後、例えば、コマンドラインから


curl -i -X POST -H "Content-Type: application/json" --data-binary '{"phones":[{"number":"789"},{"number":"012"}]}' http://localhost:8080/(プログラム名)/resources/personphone

と叩く。
phone テーブルのカラム number に “789”, “012” が入っているはずだ。

jackson でデシリアライズ

POST の方のキモは、jackson のオブジェクトマッパーを用いて、TypeReference を指定してデシリアライズしたところ。


        person = mapper.readValue(json, new TypeReference<>() {});

ここを


person = mapper.readValue(json, Person.class);

などとしてしまうと、コレクション部分(phones)を無視してしまう。
独自の型情報を使った場合、それを使った旨を言語処理系に教えないと動かないのは当たり前なんだが、どういうわけかここら辺に言及した記事はあまり見かけなかった。

おわりに

いかがでしたでしょうか?

簡単な hibernate 〜 CRUD 操作に加えて、実用的な curl コマンドの使い方や jackson の小技を入れてみました。

ところで、ワイがこういった書き方をする理由は、Java とりわけ EE 環境の解説記事に違和感を感じているから。

以前にも JavaEE/JakartaEE ではけっこう有名とされている人の記事に間違いを見つけたが、ああいう間違え方は他の言語使用者からするとありえんと思う。

筋の悪い間違え方を続けていると通常は次第に相手にされなくなっていくと思うが、本邦の JAVAer はなぜかそういうセンスのない人の言動を「あの人が言ったから」ということで重宝がる傾向があるようだ。

権威を好んでロジックをないがしろにする文化に継続的な発展はありえませんよ。

 

JAX-RS と JSON と List と hibernate

おさらい

REST api を自作してると response として List を含んだ JSON を返したいということがよくあるが、簡単なサンプルがなかったりするので、簡単なやつを作ってみた。


@Path("/persons")
public class SimpleResource {
 
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<Person> list() {
        
        Person person1 = new Person();
        Person person2 = new Person();
        person1.setName("秋葉");
        person2.setName("akiba");
        List<Person> persons = new ArrayList<>();
        persons.add(person1);
        persons.add(person2);
        
        return persons;
        
    }
    
    private class Person{
        
        private String name;

        public String getName() {
            return name;
        }

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

これをアプリケーションサーバにデプロイしてブラウザで http://…/persons にアクセスすると

[{"name":"秋葉"},{"name":"akiba"}]

という文字列が表示される。

だから何?って言われそうだが、やってないとけっこう忘れますw

返値の型は List<Person> なので、JSON で配列を表す [] が先にきて、インスタンスが二つあるので {} が二つ続くと。

 

この程度なら単純なのだが、ややこしくなってくるのは、Person クラス自体にコレクションを含むような場合だ。

ちょっとやってみよう。


@Path("/persons")
public class SimpleResource {
 
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<Person> list() {
        
        Person person1 = new Person();
        Person person2 = new Person();
        person1.setName("秋葉");
        person2.setName("akiba");
        person1.addfriend("上野");person1.addfriend("新橋");person1.addfriend("神田");
        List<Person> persons = new ArrayList<>();
        persons.add(person1);
        persons.add(person2);
        
        return persons;
        
    }
    
    private class Person{
        
        private String name;
     List<String> friends = new ArrayList<>();

        public String getName() {
            return name;
        }

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

        public void addfriend(String name){
            friends.add(name);
        }

        public List getFriends() {
            return friends;
        }

        public void setFriends(List friends) {
            this.friends = friends;
        } 

    }
    
}

Person クラスに String の List friends を追加した。

この場合のブラウザ出力は

[{"name":"秋葉","friends":["上野","新橋","神田"]},{"name":"akiba","friends":[]}]

となる。

本題 (インスタンスの List を hibernate で操作 )

実用的なシステムを組む場合、上の friends 相当箇所が別のクラス(エンティティ)になる。

Friend というクラスがあって、List<Friend> friends を定義する、みたいな。

この場合、hibernate を組み込んで、@OneToMany だののアノテーションをつけることになると思うが、巷の教科書的な解説はアノテーションの付け方を変に複雑にしているせいで、わかりにくくなっている。

次の記事でシンプルなサンプルを提示する予定。

 

Tomcat 魔改造 vs TomEE

Tomcat 魔改造

Tomcat で勘違いされていることの一つに 「Java EE 対応サーバである」というのがあると思う。

Tomcat 自体はあくまで「サーブレットコンテナ」なのだが、実際に利用する際には html などの静的なファイルも出力できないと使い物にならないためウェブサーバー機能は当然持っている。

さらに、魔法使いのような人たちが tomcat を魔改造して Jave EE(現在のJakarta EE。併記するのもアレなので以下 Java EE と表記) の機能を付け加えて使っているため、この手の誤解が生まれたと思われる。

最近だと私の周囲の上の方々がノーマル Tomcat に hibernate を使えるようにして、これを基盤に REST API を実装してしまった。

すげ…

Tomcat 自体には手を入れてないので、「そんなにすごくないんじゃない」と言われる方もいるかもしれないが、これやった人たちはいわゆる Java 特化型のプログラマーでないからね。
というか専業のプログラマーでもないっていう。。。

一体、どういう経験を積めばそんなことがいとも簡単にできるようになるのかワイには全然見当もつかなかったのだが、横で見ていて気がついたのは、技術の把握の仕方がとにかく速いってこと。

将来的にはあのレベルまで到達したいし、そうなるコツの一端には触れた気分にはなった。

が、同時に、いきなりあのレベルに到達するのは無理だというのも思い知らされましたね。

では、どうするか?

 

TomEE

幸い、私には時間もあることだし、別のアプローチを考えてみた。
そしたら、ありましたよ、興味深いプロジェクトが。

あまり引っ張ってもしょうがないので、ここら辺で本題に入ると TomEE という Tomcat に Java EE の仕様を組み込んだプロジェクトが存在した。

Tomcat + Java EE で TomEE という直球なネーミング。なお TomEE は tommy (トミー?)と発音するらしい。

公式サイトはこちら

これなら、魔法を使わなくても Tomcat ライクな Java EE 環境が手に入る!

 

TomEE WebProfile, TomEE Plus, TomEE MicroProfile, TomEE Plume

ひと口に Tomcat + Java EE と言っても Java EE の仕様の何を組み込むかによって、いくつかのプロダクトが存在しうる。

実際、TomEE WebProfile, TomEE Plus, TomEE MicroProfile, TomEE Plume という機能の異なるバージョンがリリースされている。

公式サイトにわかりやすい図があったので持ってくると以下の通り。

https://tomee.apache.org/comparison.html より

Tomcat を使う場合、ノーマルでは実装されてなくて何かと不便な EJB や JAX-RS は TomEE プロダクツ群には全て入っている。とても心強い。

逆に、若干、心配なのは、日本語で TomEE の記事がほとんどないこと。

成熟度や信頼性に問題あるのか???

 

TomEE Plus の導入

というわけで TomEE を試してみたい。

MacOS の場合、ありがたいことに、homebrew 経由で TomEE Plus を入れることができる。

brew install tomee-plus

でインストールが始まる。

現在(2022夏)だと TomEE 8.0 系列が入るようだ。Jakarta EE 9 に対応した 9.0 系列を導入したい人は、homebrew を使わずに公式サイトからパッケージを落としてみてください。
試しに使ってみる程度なら、Java EE 8/Jakarta EE 8 のどちらにも対応している 8.0 系列で十分だと思いますが。

intel Mac では /usr/local/Cellar/tomee-plus/ 以下に諸々のファイルが展開される。

/usr/local/Cellar/tomee-plus/(version)/libexec/bin に startup.sh というシェルスクリプトがあるので、これを実行する。

./startup.sh

この時点で http://localhost:8080 でマネージャーが立ち上がるのだが、ユーザー登録がまだなので登録しておこう。

/usr/local/Cellar/tomee-plus/(version)/libexec/conf/tomcat-users.xml の

<role rolename="tomee-admin" />
<user username="hoge" password="xxxxxx" roles="tomee-admin,manager-gui" />

の username と password を適当に設定。

これらを使ってログインすると

Tomcat でもお馴染みの GUI な管理画面が表示される。

なお、TomEE 自体を終了させたければコマンドライン から

./shutdown.sh

を実行する。

当然だが、とってもトムキャっぽい。

 

サンプルの実行

サンプルの一覧が https://tomee.apache.org/tomee-8.0/examples/ にある。

実際のソースコードは

GitHub: https://github.com/apache/tomee/tree/main/examples

で公開されている。

このうちノーマル雄猫(tomcat)では扱えない JPA 機能を使った jpa-hibernate プロジェクトに興味を持って動かしたのだが、test プログラムが走るのみでちょっと物足りない。

しかし、簡潔なユニットテストのコーディングスタイルにちょっとおお!となったので、この改変を試みたい。
ノーマル雄猫でも hibernate.cfg.xml などをうまく設定すると hibernate 自体は使えるんですが(『tomcat FAQ』で解説されています)、やり方がやや面倒で慣れてない人だと難しいと思います。

 

サンプルプログラム試作

TomEE 自体は Tomcat をベースにつくられているので、EE の機能をがっつり取り込んだプロジェクトでもない限り war に固めて上記の管理画面からデプロイさせれば普通に動きます。

ハロワ

私は NetBeans 使っているんですが、デフォルトの Web App のプロジェクトを作成してデプロイするとしっかりハローワールドしてくれました。

ハロワールドな index.html をブラウザからのリクエストに応じて出力させているだけですがw

サーブレット(EJB も少々)

続いてサーブレットコンテナ機能のチェック。

非同期処理のサンプルである async-servlet というプロジェクトがあったので、これを改変。

オリジナルは TomEE 9 向けに書かれていたので、TomEE 8 向けに少々手直し。

また、サンプルはそのままビルドするとライブラリ化して開発マシンのローカルリポジトリに収納させる仕様になっているようで、この点も .war ファイルを吐き出してくれるように改変。

(追記)ソースコードは air-h-128k-il 氏のリポジトリに置かしてもらいました。
GitHub: https://github.com/air-h-128k-il/async-servlet2

 

まず、適当に作成した index.html を表示。

ここは問題ないでしょう。

リンクを踏むと http://localhost:8080/async-servlet2/calc に飛ぶ。
これがサーブレットで書かれているので、表示できればOKなんですが。。。

お、表示された(↓)。

このサーブレットは、GET リクエストのパラメータ(クエリ文字列)を受け取って四則演算の結果を返してくれるんですが、パラメータがないと上の画面にあるように使い方を表示してくれます。

このサーブレットの面白いところは非同期処理を使っているところ。
リクエストを受け取ってから結果を表示させるまでの遅延時間やタイムアウト時間が設定できます。

実際、Example にあるように

http://localhost:8080/async-servlet2/calc?x=2&y=4&op=multiply&async=true&delay=1000

にアクセスすると、やや遅れて(正確には 1000ms=1sec なんでしょうけど)2 x 4 の結果の 8 が表示されます。

なお、サーブレットの非同期処理は servlet 3.0 から導入された機能なので、サーブレットはノーマル雄猫の 8 でも動くはずです。
これ自体は Java EE の仕様とは認知されていないと思います。
詳しくは『Servlet標準の非同期処理に触れてみる』などを参照してください。

なのですが、実は、ここで Java EE 要素が軽く出てきています。
このサーブレットは、以下のように CalcBean.java を EJB として扱っています。

CalcServlet.java
	@EJB
	private CalcBean bean;

EJB は、Java EE の仕様なのでこの部分に関してはノーマル Tomcat では動かないと思います。具体的な挙動は『EJB の話』に書いてます。

徐々に Java EE らしさが出てきたでしょうか。

もうちょっと苦労するかと思いましたが、あっさり動きましたね。

TomEE なかなかやる?

いよいよ JPA hibernate

サンプルの素にしようとした jpa-hibernate ですが、pom.xml をよくみると openejb-core-hibernate が使われている。。。
これ、純正 hibernate ではなく、あくまでユニットテスト用の hibernate ですよね。

さらに、ejb-jar に相当すると思われる機能も使っている。。。

インターセプターとかあそこらへん苦手なんですが。

これは、簡略化して一からプロジェクト作成ですね。

 

結局、諸々調整して、シンプルな構成のオブジェクトを永続化させるサーブレットに仕立て上げた。

ソースコードの主要な部分は以下の通り。

Movie2.java

@Entity
@Table(name = "movie2")
public class Movie2 implements Serializable {

    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String director;
    private String title; 

    (以下略)
Movies.java

@Stateful
public class Movies {

@PersistenceContext(unitName = "movie-unit", type = PersistenceContextType.TRANSACTION)
private EntityManager entityManager;

@Inject
private Movie2 movie;

public void addMovie(Movie2 movie) throws Exception {
entityManager.persist(movie);
}

(他にも関数は作成したが、実際使っているのはこれだけなので以下割愛)

 

実際の動作としては、まず、永続化させたい文字列をブラウザ上で指定。

フォームを使って、セットした文字列を GET でサーブレットに投げる。

サーブレットは、Movies の EntityManager を利用してこれらを永続化。

 

実際、PostgreSQL のテーブルをのぞいてみると

テーブル自体を作成して、ブラウザ上で指定した文字列を該当カラムに記録していますね。

いやあ、JPA も利用できるものなのですね。

ちょっと設定でまごつきましたが(笑)

 

あと、面白いのは、データソースは JTA で指定してありますが、

 情報 [http-nio-80-exec-233] org.apache.openejb.config.AutoConfig.setJtaDataSource Adjusting PersistenceUnit movie-unit <jta-data-source>
 to Resource ID 'jpa-hibernate5/jdbc/moviedb' from 'jdbc/moviedb'
 情報 [http-nio-80-exec-233] org.apache.openejb.config.AutoConfig.setNonJtaDataSource Adjusting PersistenceUnit movie-unit <non-jta-data-source>
 to Resource ID 'jpa-hibernate5/jdbc/moviedbNonJta' from 'null'

と nonJTA のデータソースも自動で作っている点でしょうか。
内部的にどういうふうに利用しているのかわかりませんが。

JAX-RS で REST

ここまできたら、REST なウェブサービスも作ってみたい(笑)。

シンプルにこんなコードを書いた。

import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;

@Path("/hello")
public class Hello {
    @GET
    public String responseGet() {
        return "Hello GET";
    }

    @POST
    public String responsePost(String message) {
        return message;
    }    
}

これを TomEE にデプロイしてコマンドラインから curl で API を叩く。
GET:

% curl http://localhost/hogerest/hello
Hello GET

POST:

% curl -X POST -d 'hello post' http://localhost/hogerest/hello
hello post

プロジェクトに取り込むライブラリの中に jakartaee-api が含まれていれば、javax.ws.rs.Path などは見つかるはずなのでビルド自体はできる。

ただ Tomcat にデプロイした場合は JAX-RS は実装されてないので、上記のコマンドを叩いても 404 が返ってくるはずだ。

TomEE けっこう使えるのでは?

 

TomEE の使い所

思っていた以上に使えるというのがざっと試してみた上での感想。

特に EJB や JAX-RS などの機能を簡単に使用したい場合、特別な工夫なしで使えるので便利だと思った。
Java でウェブアプリを作成する際、デプロイするコンテナやアプリケーションサーバのことを気にかけておかないと、後でとんでもないことになるが(要するに Tomcat か WildFly かなりを予め決めておかないと移行作業が大変になる)、ここら辺あまり意識しなくてもいい感じがする。
これは、プロジェクトの初期段階でコーディングする際のかなり大きなメリットでしょう。

ただし、JavaEE の機能をガシガシ使うようなプロジェクトの場合、TomEE で本当にイケるのかというと(そこまで使い込んでないので)正直よくわからない。

そもそも、そこまでガシガシ使うなら、最初から WildFly や GlassFish 選べばいいという気がしないでもない。

だから、サーブレットや JSP の使用多め、けど、JPA 経由で hibernate などを軽めに使いたいなんてプロジェクトには向いているかもしれない。

これにしても Spring 系でいいじゃないか?という声も聞こえてきそうだが、ワイは Spring 系まともに使ったことないのでよくわかんなんだよね。そういうわけで比較できません(笑)。

Java 自体、そんなに使い込んでいるわけじゃないしね。

ただ、本格アプリケーションサーバや Spring などとの競合というのは、当たっているかもしれないなと思うところはある。

というのは TomEE 自体の完成度の割にコミュニティの規模が小さいと感じるから。

日本語で書かれた情報って本当に少ないでしょ?
Spring 関連の情報なんてググれば山ほど出てくるのに。

TomEE 本家のフォーラムも覗いてきたが、トラフィックはやはり少ない。

 

かなり強引にまとめ

Tomcat に慣れていて、JavaEE の機能をお手軽に追加したいって人には向いていると思います。

魔法のような特殊設定しなくても JavaEE の主要な機能が使えます(笑)。