ORM と infinite recursion の問題

これまで、hiberante などの ORM を使ったサンプルコードで双方向の関係を取り上げなかった理由は、いわゆる infinite recursion (無限循環)の問題にけっこう悩まされるから。

このサイトでは、対象を初学者〜中級者に想定しているが、おそらくそういった層に infinite recursion に関係するバグが発生しかねないサンプルを掲げたら、混乱は必至でしょう。

それで、意識的に避けてました。

無限循環は大抵の場合、<親-子-孫-・・・> の関係があるときに発生する。

つまり、親-子が1対多であるときだ。

この場合、例えば、hibernate であれば、親側の子を示す変数(データベースではカラム)に @OneToMany のアノテーションをつけ、子側に親の当該変数を指し示す変数に @ManyToOne のアノテーションを付与する。

教科書的にはこれで双方向の関係が定義できたことにはなる。

何も問題なさそうじゃないか?という声が聞こえてきそうだが、実際には困った挙動を示すことがある。

子のオブジェクトを生成し、これを JSON などで表示したいときに、当然、親側の情報も持っているから、ORM はこれを引っ張ってくる。
ところが、親は子の情報も持っているから、そこから子の情報を探そうとする。
探しだされた子は、さらに親の情報を取り出そうとして・・・

と、立派な無限循環が完成してしまう(笑)。

具体的にはこういうやつ↓です。


Infinite recursion (StackOverflowError)
(through reference chain: model.Parent["child"]
->org.hibernate.collection.spi.PersistentBag[0]
->model.Child["parent"]->model.Parent["child"]
->org.hibernate.collection.spi.PersistentBag[0]
->model.Child["parent"]->model.Parent["child"]
->org.hibernate.collection.spi.PersistentBag[0]
->model.Child["parent"]->model.Parent["child"]
->org.hibernate.collection.spi.PersistentBag[0]
->model.Child["parent"]->model.Parent["child"]
以下、無限に続く。。。(笑...えない)

使い慣れた ORM であれば、どこら辺が危険かある程度わかっているから、経験的に回避できなくもないが、仕様もうろ覚えのような ORM ではひとたびこの問題が発生したらハマること必至でしょう。

意識的に避けてきた理由はそこにある。

特に初学者に人は、まず習得したい ORM がどう動作しているか視覚的にわかるようなところから始めるといいと思う。

以前に Person – Phone の例を挙げたとき、person_phone などという一見邪魔くさいテーブルが生成されたが、わからなくなったら意識的にああいうテーブルをつくったらいいと思う。

遠いようでいて速いと思う。

 

参考

こことか。探せばいくらでも出てきそう。

コメント返し

コメントにこういうのがあった。
なんか、私の近くにいそうな人(ツィ友?)のような気もするが…。

>双方向はバグの温床となる気がするので、片方向だけでなんとかするというアプローチはありでしょうか?

ううむ。
やってやれなくはないので、そうしても構わないと思います。システム構築が大変だと思いますが。
ですが、無限循環が問題になるのはキケンな関係にあるクラス(のインスタンス)をダイレクトに参照しようとするときです。

実用的には、こういった直接参照を使わないということでもいいと思います。

具体的には、表示したいオブジェクトがあるときは、まず、その上流のオブジェクトにアクセスし、ゲッターで本当に操作したいオブジェクトを取得する、みたいなやり方です。

あるいは @JsonIgnore あたりのアノテーションを駆使するとか。

循環参照への対策

Resourceクラス(JavaBean)をJSONやXML形式にシリアライズする際に、相互参照関係のオブジェクトをプロパティに保持していると、循環参照となりStackOverflowErrorやOutOfMemoryErrorなどが発生するので、注意が必要である。

循環参照を回避するためには、

Jacksonを使用してJSON形式にシリアライズする場合は、シリアライズ対象から除外するプロパティに@com.fasterxml.jackson.annotation.JsonIgnoreアノテーション

JAXBを使用してXML形式にシリアライズする場合は、シリアライズ対象から除外するプロパティにjakarta.xml.bind.annotation.XmlTransientアノテーション

を付与すればよい。
https://terasolunaorg.github.io/guideline/current/ja/ArchitectureInDetail/WebServiceDetail/REST.html 

この手のアノテーションは便利だとは思います。
ですが、これらを多用すると可読性が落ちる気がしますし、仕様が変わってしまえば「動かない」コードになってしまいがちです。

システムならなんでもそうだと思いますが、基本設計をなるべくシンプルにして、どうしようもないところは(姑息的になるかもしれませんが)ワザを繰り出していくというのがワイの基本ポリシーです。

今回のネタに関して言えば、ここぞというところでは双方向も使っていいんじゃないでしょうか。

(追記)
ORM には循環参照がつきものと考えたため、この記事を起こしたのだが、全く循環参照を気にしなくていい便利 ORM も存在する。
このブログでも頻出の CoreData。
ライブラリ側でよしなにやってくれるようだ。
逆に絶対に意識してデータ構造を組まないとダメなのは、やはり hibernate。
ワイが注視していることプロジェクトがあるのだが、思いっきりコケそう。
convertor パターンを崩したら、その分、補償する必要があるのだが、通常のマッパーで代用効くのか?
しかし、このプロジェクト、第三者からの対応が下手くそすぎる。

 

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

 

 

PostgreSQL 使用上の注意点

日頃、お世話になっている PostgreSQL 。

ここでは難しい話ではなくて、知らないとハマるポイントなど。

テーブル user はつくれない

PostgreSQL では user というワードは予約語になっていて、この名前をテーブルにすることはできない。

なのだが、やらかしている人は世界中にいるっぽい。

まあ、ポスグレに限らず基本的なワードは個別用途に使わない方が無難なんだが、MySQL では使っても問題ないらしい。

(適宜追加予定)

 

HTML のテキスト入力要素

凝ったことをやっていると基本を忘れてしまう。

HTML のテキスト入力要素について。

単一行テキスト入力

input 要素を使う。

<input id="name" name="name" type="text"></input>

複数行テキスト入力

textarea 要素を使う。

<textarea name="comment"></textarea>

wordpress だとこんな感じになるんですね。

 

JavaScript -連想配列と配列でオブジェクトを表現-

今日は Java ではなくて JavaScript の話。

初学者向けの JavaScript の記事、ヤバいのが多くないか?

この前の記事でモデル Phone のコレクション(リスト)を含む Person を永続化する REST api を作成したが、JavaScript で Person のオブジェクトを投げたい。

その際、配列と連想配列を用いて Person の実体を作成する必要があるんだが、配列と連想配列を組み合わせて使うという発想がないらしく、そのままでは動かないサンプルが散見された。

JavaScript は詳しくないが、おそらく「動く」コードは以下のような感じ。


var phone1 ={'number':'090-111-1111'};
var phone2 ={'number':'090-222-2222'};
var phones =[phone1,phone2];
var person = {'phones':phones};
var json_text = JSON.stringify(person);
console.log(json_text);//確認用
// {"phones":[{"number":"090-111-1111"},{"number":"090-111-1111"}]}

この処理をしてから、XMLHttpRequest などを使って REST api に POST する。
ちなみにちゃんと永続化できてた。

配列の宣言・初期化は [] 、連想配列の宣言・初期化は {} 、が基本だと思うのだが、おかしな記法や説明している記事が多かった。

思うに、オブジェクトを配列・連想配列を組み合わせて JSON で表現する、という実用的な使用方法を意識してないんだろうなあ。

スクリプト系言語の初学者向けの記事には気をつけた方がいいかな?