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 だとこんな感じになるんですね。

 

サジタリウス杯2022

前回のチャンミのまとめが飛んでいる気がしないでもないが(笑)、サジ杯2022の結果まとめ。

まだ、ラウンド2なのだが、サークル主要メンバーが全て A に進んだので、記しておきたかった。

今回は12月開催ということもあって、みんな「参加だけしておこうか」みたいな感じだったのだが、意外に結果が良かった。

おそらく

・チャンミ参加者自体が減っている

・引退者も多くレベルがバラけてきた

あたりが背景にあんのかなと。

ラウンド2になって、仕上がった逃げが多くなってきたので、A 決勝に進むにはそれなりに苦労すると思うが、ラウンド1なら(私が好きな)先行あたりでも結構通用した。

サポカの脚質・距離別の専門特化が進むにつれ、全ての個体にチャンスが出てきた感じがする。

開催条件に合った星3強キャラがいたとしても、星5で覚醒レベル・ヒントレベルの上がったキャラにはなかなか勝てないということなんだと思う。

一昔前のゴルシゲーみたいなのは懲り懲りという印象を持っているワイなんかからすると、歓迎すべき状況だ。
当時は、サポカの充実度で勝敗が決定する、というバカみたいな状況があった。