ものぐさ RESTEasy -画像データなどを REST っぽく返したい-

『ものぐさ Jersey』的なやつが好評のようなので、その RESTEasy 編。

いきなりで恐縮だが、REST なウェブサービスを作っている際に画像も REST 的に取り扱いたいときがある。
要するに適当なエンドポイントを叩くと画像ファイルが返ってくる、みたいなやつだ。

サーブレットで実現するのが一番簡単なようなんだが、サーブレットを使わない方向でチャレンジする。

リンクした記事は jsp で書いてあるが、ちなみにサーブレットで書き直すとこんな感じ↓になる。

あっさり成功。

 

で、RESTEasy 。
まずは公式ドキュメントのチェック。

レスポンスを適当に加工すれば、なんとかなるだろうくらいに思って調べてみた。

なんだが…

なんでしょう、この突き放された感じ(笑)。

jakarta.ws.rs.core.StreamingOutput を使って自分で実装しろ的なことが書かれている。

いや、そんなことをするくらいならば、サーブレットのやつそのまま使いたいかなあ(困惑)。

アウトプットストリーム系を使って、とりあえずバイト列を返す

他の人がどうやっているかよくわからないのだが、まずはシンプルに攻める。

(公式ドキュメントのサジェスチョンを尊重して)バイト列を生成して、そいつを ByteArrayOutputStream にセットし、さらにそれを Response の中(Entity とかいうみたいだ)に入れ込んで REST っぽく返す、と以下のような感じになる。


@Path("/octet")
public class OctetServer {

 @GET
 @Produces(MediaType.APPLICATION_OCTET_STREAM)
 public Response sendOctet() throws IOException{

  byte[] byteData = "123456789ABCDE".getBytes();

  ByteArrayOutputStream output = new ByteArrayOutputStream();
  output.write(byteData);

  return Response.ok(output.toByteArray(), MediaType.APPLICATION_OCTET_STREAM).build();

 }

}

これで curl で (コンテクスト)/octet を叩くと、もちろん 123456789ABCDE がバイナリで返ってくる。
ブラウザからアクセスすると octet というファイルを落としてくれるが、中身は当然 123456789ABCDE だ。

これで、第一段階はクリア…

と思っていたが、コード見直すと、バイト列をストリームにセットした後、さらにバイト列に戻す、というバカっぽいことをやっている(苦笑)。

バイト列をエンティティとしても同じ

だから、上のサンプルはもうちょっと簡単になる。

具体的には、

return Response.ok(output.toByteArray(), MediaType.APPLICATION_OCTET_STREAM).build();

は、

return Response.ok(byteData, MediaType.APPLICATION_OCTET_STREAM).build();

としても変わらない。

もちろん、その上の2行は全く不要。

とりあえず、バイナリのデータを返すだけであれば、アウトプットストリーム系は不要で返したいデータのバイト列を response のエンティティにセットすればいいだけのようだ。

指定した画像などを返したければ、path parameter を設定してデータベースから検索して云々とやれば、実現できそう。

 

 

H2 database

使われている割に存在感が希薄な H2 database。(WildFly ではデフォで使える)

調べたら、ホームページもあった。
https://www.h2database.com

使い方はそこの Cheat Sheet にまとまっているので、特に付け加えることもないのだが、サーバーモードというのは知りませんでした。
ちゃんと調べてみるものだね。

H2 console を単体で使いたければ、h2-(version).jar を入手して、ダブルクリックで起動、ブラウザ画面が立ち上がる。

pgAdmin や phpMyAdmin みたいな感じのユーティリティソフトです。

 

JakartaEE 各仕様まとめ

JakartaEE 関係の記事が増えてきたような気がするので、ここら辺でまとめ。

JakartaEE 各仕様

各仕様毎に軽めの記事を書いてきたつもりだったが、読み返したら微妙にオーバーラップしてる。

JAX-RS

ものぐさ Jersey』WildFly は RestEasy だが、GlassFish, Payara は Jersey なので取り上げた。が、最後の方で Jackson 成分も混じっている。

JAX-RS と JSON と List と hibernate』これも、REST の話から始まって、JPA(hibernate) につながっている。

JPA

実は JPA の仕様は資料にあたったことはない。実務的には hibernate でデータベース操作ができればいいでしょう、というわけで書いたのが

最も簡単な hibernate のサンプル

ここら辺から、「読みやすい」と言われるようになってきた。

CDI

初学者泣かせの CDI 。

CDI と weld の関係

で取り上げたが、なくてもなんとかなるので、無理して @Inject とかしなくていいかも。

つか、参考書の類で CDI の説明でわかりやすいのがないと思う。初学者にいきなり「依存性を注入して…」とか言ってもなんのことかさっぱりわからないと思う。

JAXB

Java と XML -JAXB の話-』理解があやふやだったので、自分の勉強のために書いた記事。実際、勉強になった。


他にもあるが、代表的なのはこんなところでしょうか。

 

 

Java と XML -JAXB の話-

これまで、Java ⇄ JSON みたいな話はしてきた。

このご時世、JSON 大流行りなので、大抵はこれで方が付くんだが、もちろん情報交換のフォーマットとして今でも XML フォーマットは使われている。

ここら辺は「さすが Java」という感じなのだが、Java ネイティブ型 ⇄ XML の変換を取り決めている仕様が存在する。

それが Java Architecture for XML Binding というやつで、JAXB などと呼称されている。

必要性は低くなってきているが、無視はできない

ところで、正直言わせてもらえれば、JAXB の話をするのはあまり気乗りがしていない。

というのは、これまで、この仕様を使わなくてもなんとかなってきたから。

XML を解析するだけであれば、

・jdom(2) を使って解析→コレクション(list や map)を用いて手動で適宜後処理

という手法でも実務上あまり困らないと思う。

これはワイに限ったことではなく、JAXB が Java の正式仕様から外されたり復活したりしている理由は背景にこういった状況があるからだろう。

時代は良くも悪くも JSON 形式なのだ。

ただ、少々汚い形式の XML 文書を Java のオブジェクトとしてプログラム内に取り込んで自由自在に扱うというのは「映え」はする。

なんか釈然としないが、気を取り直して・・・。

準備

マッピングすべきクラスのフィールド変数が基本データ型だけで構成されていればそんなに難しいことはないんだが、配列や list が含まれていると難易度が上がる。
印象としては xml では、要素のまとまりなどは配列的に使われていることが多いと思うが、Java などではこれを list として取り扱いたいということが多いと思う。

配列⇄List の相互変換

Java では、配列と List を相互変換できる。

・配列(Array)→ List のときは Arrays.asLIST

・List → 配列(Array)のときは toArray

を使うのが原則。

List<String> items (= [item1, item2, item3]) があったとき、これを配列 String[] array にしたければ

String[] array = items.toArray(new String[items.size()]);

とする。

逆に array を items に戻したければ

List<String> items = Arrays.asList(array);

とする。

ただし、以下のサンプルコードが示すようにこの小技はあまり使う必要がないかも。

サンプルコード

えいやっと書いてしまった。

Parent.java
public class Parent {

private int id;
private String name;
private List<Child> children;
(以下、セッターゲッター)

せっかく配列や List の話もしたので、Child.java も設定。


public class Child {

private String name;
(以下、セッターゲッター)

この状態でオブジェクトに適当な値を設定。

JAXB.marshal(parent, System.out);

などとすると標準出力に以下のような xml 文書を吐き出してくれる。


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<parent>
<id>1</id>
<name>アグネスタキオン</name>
<children>
<name>ダイワスカーレット</name>
</children>
<children>
<name>ディープスカイ</name>
</children>
</parent>

逆に XML→ Java オブジェクトにしたければ、

JAXB.unmarshal(new StringReader(xml), Parent.class);

などとすればいい。

食わず嫌いでした

実際に手を動かすと意外に使いやすい。

ジャクソンの場合、手を入れる必要がある箇所でも marshal/unmarshal で済んでしまう。

これは食わず嫌いだったかな。

 

参考記事

https://qiita.com/opengl-8080/items/f7112240c72d61d4cdf4

 

 

もっとも簡単な 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 はなぜかそういうセンスのない人の言動を「あの人が言ったから」ということで重宝がる傾向があるようだ。

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