yamicha.com's Blog - Presented by yamicha.com
Blog yamicha.com's Blog - 2018/06 の記事
[1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] [16] [17] [18] [19] [20] [21] [22] [23] [24] [25] [26] [27] [28] [29] [30]

yamicha.com's Blog
 諸事情により、現在更新休止中。ご了承ください。もし今後ブログを再開することがあるとすれば、その際にはこのブログスクリプトではなく、新しく開発したものによるかもしれません。
 当ブログ管理者についてはこちらをご参照。
開発魔法(737)
社会問題(733)
お知らせ(11)
質問・バトン回答(15)
ゲスト出演(8)
経済・知的財産(150)
ゲーム開発(182)
[Ada] 勝手に補足
- Note
- 金配りの次の一手


- Endless Ultimate Diary
- 銃世界

漢字バトン
- うるる雑記帳
- 漢字接力棒

ツキアイゲノムバトン
- ブログ@うにうに画像倉庫
- あぶ内閣

縺イ縺セ縺、縺カ縺励ヰ繝医Φ
- 月夜のボヤキ
- 騎士サーラバトン
パスワードを使う
名無し (2012/02/27)


開発者解放裁判
yamicha.com (2010/03/14)
Winnyに関しては、私も「純白」とまでは考えておりませんし、使用し..

開発者解放裁判
通りすがり (2010/03/08)
winnyに関しては「ダウンロードソフト板」なんてところを拠点に開発..

新型インフルエンザの恐怖
いげ太 (2009/11/03)
> C#などの「int Some(int , int)」は、F#では「(int * int) ->..

時効に関する思考
yamicha.com (2009/08/31)
>いげ太さんコメントありがとうございます。手元にドキュメントが少..
Homepage
Blog Top
Normal View
List View
Search
Calendar
Comment List
Trackback List
Blog RSS
Send Trackback
Phone Mode
Administrator
yamicha.com
Blog
るううるる。
Source
法令データ提供システム
FindLaw
Development
Java2 Platform SE 6
Java EE 6 API
MySQL Developer Zone
PHP Reference
MSDN Library
Ada Reference Manual
Objective Caml
Python Documentation
Erlang
Prolog Documents
捜査権力乱用排除対策記事
2010/01/04(Mon)23:21:58
 2009年12月22日、政府は犯罪対策閣僚会議を開き、「児童ポルノ排除対策ワーキングチーム」なる対策チームを設置しました。9つの省庁で連携して児童ポルノへの対策を調査・検討するもので、6月にも報告書をまとめるとしています。実際にどうなるかは不明ですが、この報告書の内容やチームの提言によっては、後に何らかの法改正がなされる可能性も否定できません。
 法改正の話はかねてから存在しており、決して今回突如として出てきたものではありませんし、民主党は自民党に比べて規制強化に慎重な姿勢を取っていますので、急進的な規制強化の可能性は政権交代以前よりもむしろ縮小していると考えるべきでしょう。しかしながら、本問題をめぐるマスコミ報道には非常に不適切、あるいは恣意的なものが散見され、そのような記事にだまされてしまう人がいないとも限りませんので、以下に問題点をまとめておきます。
 2009年11月17日、読売新聞が自社のサイト上で「民・自・公が児童ポルノの単純所持を禁止する改正案をまとめた」旨の報道を行いました。かねてから民主党は児童ポルノの単純所持禁止に慎重な姿勢を見せており、政権交代によって単純所持の規制は遠のいたとするのが大方の見方ですので、この記事に従うなら民主党は唐突に方針を転換したことになります。
 ところが、この記事はまもなく削除されてしまいました。代わりに掲載されたのは、「自民党が」そのような改正案を提出するという内容の記事でした。その後、読売新聞が本件の訂正や謝罪を行ったとの続報は見当たらず、前の記事は「なかったこと」にされてしまったようです。同社からの公式なアナウンスが存在しないとみられる以上、なぜこのような誤りが発生したのかは不明ですが、自民党側の不確実なリークに基づく不確かな記事であるとも、世論の動向を見るための「観測気球」であるとも考えられます。
 読売新聞は以前より児童ポルノ規制に積極的なスタンスを取っており、社説などでも単純所持を規制すべきと主張していますが、本件の手口はさすがに陰険過ぎると言わざるを得ません。何らかの問題に複数の結末が考えられる場合、新聞社はあらかじめ複数の記事を用意しておくといいますので、今回のミスも単に記事を取り違えただけと考えられないわけではありませんが、それならそれで経緯を洗いざらい公開して謝罪すれば何の問題もないはずです。それをひっそり闇に葬ろうとしている以上、大っぴらに謝罪して誤記事配信の事実が知れ渡ると困る事情、すなわち偽情報リークや観測気球などを疑われても仕方がありません。
 その本題の児童ポルノ規制問題ですが、私は児童ポルノを規制すること自体に反対するつもりはなく、むしろ不十分な部分があるなら強化も必要であると考えています。実写の児童ポルノには必ず被害者が存在し、児童ポルノ作成とはすなわち性的虐待や搾取を意味しますので、決して作成を許容するわけにはいきません。また、写真などの媒体の受け渡しによって無尽蔵に被害が拡大してしまうため、流通に歯止めをかける仕組みも必要です。これらの規制の必要性に関しては、ほとんどの人が賛同するのではないでしょうか。
 しかしながら、単純所持となると話は違ってきます。単に写真などを所持するという「状態」ではなく、積極的な入手など何らかの「行為」が立証されることを罪の構成要件とし、しかも適用範囲を厳密に定めるなど、極めて厳密・厳格な法案が作成されたのであれば、私もそれに賛成する用意はありますが、あいまいな要件による単純所持の禁止には慎重にならざるを得ません
 今からおよそ2000年前、中国には政敵などを陥れる呪術がありました。今で言う「わら人形」のようなもので、迷信の一種です。ところが、実はこの呪術はただのまじないなどではなく、実際に大変強力な効果を持つものでした。なぜなら、時の政権がこれを厳しく規制したためで、政敵が呪術を行ったように見せかければ、文字通り相手を「呪殺」することができたのです。
 児童ポルノの単純所持規制にも、同様の問題が懸念されます。そもそも「児童ポルノ」の定義があいまいであるため、自分自身や我が子の写真が児童ポルノとみなされ、単純所持容疑で逮捕される可能性すらあります。無論、警察としてもアルバムに子どもの裸の写真がある家庭すべてを回って逮捕しているわけにはいきませんので、厳密には「違法だが黙認されている」状態になるものと予想されますが、それすなわち「いつでも法を適用して逮捕できる」状態を意味します。
 児童ポルノの要件を厳格に定めようにも、水着なり露出の多い衣服なりはどこまで許されるのか、自分自身や我が子などの裸の写真が罪に問われるのは理不尽ですが、それなら誰の写真を誰が所有するなら合法なのか、被写体が誰なのかはっきりしない場合はどうするのか、年齢の判別が困難な場合はどうするのかなど、無限に存在する疑問に対して逐一定義を与えるのは不可能ですから、最終的には「18歳未満と思われる人物が写った、わいせつな写真その他の媒体」のようなあいまいな定義になるのは見えています。
 それでは、一体何であれば「わいせつ」で、どのように年齢などを判断するのでしょうか。以前に「自衛隊を派遣したところが非戦闘地域だ」なる珍妙な主張を行った人がいましたが、実際問題として「摘発されたものが児童ポルノ」となる可能性が高いのです。何の変哲もない水着の写真であれ、明らかにわいせつに見えない写真であれ、自分や我が子の写真であれ、平気で児童ポルノ扱いされて逮捕されてしまう恐れがあります。
 これは決して大げさな話ではなく、現実に発生する可能性が十分に考えられる事柄です。例えば、警察に何らかの事件への関与を疑われた場合、その事件に関する証拠が十分にそろっていない状況では、警察は「別件逮捕」の手法を取ることがあります。何でも良いので適当な罪状で逮捕し、そこで強引に自白を取って起訴に持ち込もうという戦術です。あるいは、何らかの罪状で一旦逮捕した後、引き続き取り調べを続けるために再逮捕を繰り返す場合もあります。
 すなわち、何らかの事件に関して事実無根の嫌疑をかけられた場合、警察が何かしら理由をつけて家宅捜索を実施し、アルバムから自分や我が子の裸の写真を見つけるなり、適当な写真や映像、HDD内の画像などポルノでないものを児童ポルノ呼ばわりするなりして、不当に逮捕されてしまう可能性が十分にあるのです。もし容疑をかけられている事件が性犯罪であったりすれば、マスコミも大騒ぎしてほぼ確実に犯人にされてしまうでしょう。
 また、検索などで偶然に児童ポルノサイトに入り込んでしまったり、ウイルスやクラッカーの仕業で児童ポルノがHDD内などに保存されてしまう可能性もあり、外国では実際にウイルスによって児童ポルノがダウンロードされ、逮捕されかかる事態が発生しています。また、インターネットには「Webビーコン」と呼ばれるトラップがあり、簡単に言えば画像を目に見えない方法でこっそりWebページに埋め込むものなのですが、これでも画像は知らない間にダウンロードされてしまうため、逮捕される恐れがあります。
 このように、単純所持の規制を安易に認めるのは非常に危険で、しかもこれは「仮定の話」や「最悪のケース」ではなく極めて現実的なリスクです。しかしながら、マスコミの多くはこの懸念をそれほど大きくは取り上げておらず、あたかも単純所持規制が絶対的正義であるかのように報道するケースさえ見受けられます。仮に単純所持を何らかの形で規制するにしても、この懸念に関する議論は欠くべからずものであり、隠蔽することが許されるわけがありません。
 果ては、一部には創作物の作成や所持までもを規制しようとする動きさえありますが、論じる価値もないと言わざるを得ません。以下、毎日新聞の「クローズアップ」からの引用です。
 児童ポルノ根絶などに取り組むNGO「国際ECPAT」のカルメン・マドリナン事務局長が来日し、国際情勢や日本の現状への危機感を次のように語った。

 現在、インターネット上では毎日新たに200枚もの児童ポルノ画像が増え続けている。国際刑事警察機構(インターポール)によれば、性的な画像を撮られネット上にさらされている子の数は1万人から10万人、その83%は6歳から12歳だ。より暴力的で低年齢化する傾向もある。

 規制に対しては「芸術的表現への検閲が強まる」「画像を見ることで実際の虐待が減っている」との主張もある。だが各国の法執行機関などが持ち寄ったデータでは、起訴された画像所有者の約4割が実際の性的虐待に関与し、約15%が試みようとしている。画像を見ることと性的虐待には因果関係があると言える。

 コンピューターや漫画などバーチャル(疑似的)な画像の規制も課題だ。国際的には児童ポルノや虐待の定義を広げる流れにある。日本は世界的にみてバーチャルな児童ポルノの生産・輸出国で、まねようとする国も出てきている。無視できない現実だ。

2009/12/31 クローズアップ2009:児童ポルノ 画像バーチャル化「日本は生産・輸出国」より引用
 ざっと読むと分かりづらいのですが、これは最初の段落を除いてすべてECPATのカルメン・マドリナン氏の主張を記載したものであって、何らかの裏づけのある新聞記事というわけではありません。しかしながら、どう見ても明らかに不当な言い分が散見されるにもかかわらず、新聞社として誤解を招かないための注釈や問題提起を入れないのは、良く見積もっても怠慢、悪く見れば世論操作であると指摘されても仕方がありません。
 表現としてまず問題なのは、以下の「事実と言わんばかりの憶測」です。
だが各国の法執行機関などが持ち寄ったデータでは、起訴された画像所有者の約4割が実際の性的虐待に関与し、約15%が試みようとしている。画像を見ることと性的虐待には因果関係があると言える。
 この「因果関係があると言える」の部分はあくまで主張者の憶測です。かなり強い確定的表現がされているため分かりづらいのですが、所詮は主張者がそのように推理しているだけですので、「因果関係が存在する余地がないとは必ずしも言い切れない」と言い換えても何ら問題はありません。マドリナン氏が実際にこのような強い言い方をしたのであれば、氏には世論誘導の意図があると考えて間違いありませんし、一方で毎日新聞が勝手に要約なり翻訳なりしたのであれば、同社の姿勢に問題があります。
 論の中身にしても、まず「各国の法執行機関などが持ち寄ったデータ」なるものが大変にあいまいで、いかなるものであるのか全く分かりません。国ごとに規制の基準も違えば警察の能力も違う以上、データの選び方次第でいくらでも任意の結論を作り出せます。ただ、データそのものを疑っては話が進みませんので、ひとまずデータは正しいものと仮定してみます。
 ところが、このデータの分析がまた恣意的です。「起訴された画像所有者」と表現されているからには、単純所持や入手が規制されている国々のデータであると推測されますが、「規制されている国であえて画像を入手・所持する」行為は、規制されていない国でのそれとは訳が違います。他の問題に例えるなら、日本で「対人用の拳銃を持っている」として起訴された者の何割かが実際に人を殺傷していたとして、拳銃の所持が違法ではない米国でも拳銃所持者が同様の割合で人を殺傷していると考えるのは誤りです。したがって、少なくとも本件を日本に当てはめて考えるのは不可能です。
 また、児童ポルノ画像を所持あるいは入手したとしても、その中で起訴される者はごく一握りに過ぎず、氷山の一角であることは容易に想像できます。多数の所持者がいながら一部の人間のみが起訴される理由は、警察などの捜査網に引っかかったり、「偶然」といった言い訳ができないほど作為的に画像を入手した証拠が積み上がっていたり、捜査機関としても目こぼしができなかったりするほど大っぴらに、あるいは大量に児童ポルノを入手または所持、場合によっては作成していたためと考えるのが自然で、児童ポルノが規制されている国においてそれだけ危険な行為を取っている人というのは、児童ポルノを所持あるいは入手していても目立った行動はしない人に比べ、性的虐待に関与する確率が高いと考えても矛盾はありません。
 さらに、ごく常識的に考えて、「児童ポルノの所持」と「実際の性的虐待への関与」では、発覚する確率はどちらの方が上でしょうか。児童ポルノを所持していることなど容易に分かるものではありませんので、おそらく大半の人が後者と答えるはずです。それでは、「児童ポルノの所持が分かって起訴し、調べてみたら性的虐待に関与していた」ケースと「性的虐待が発覚し、余罪を追及したら児童ポルノを持っていた」ケースでは、どちらの方が発生しやすいといえるでしょうか。これでは、「犯罪者を調べたところ、そのほぼ全員が水分を摂取していた。すなわち、水分を摂取する行為は犯罪と因果関係がある」と定義するようなものです。
 加えて言えば、「因果関係」を主張するからには、「児童ポルノの所持」という原因があったからこそ「性的虐待」の結果を生み出した、すなわち児童ポルノの所持が犯罪を誘発するという証明が必要で、それが無理ならどうがんばっても「相関関係」を提起する程度が限度です(ただし、その相関関係の存在すら怪しいのは前述の通りです)。仮定としても絶対にあり得ない数値ではありますが、もし仮に「全世界の児童ポルノ所持者を1人残らず全員調べたところ、その4割が性的虐待に関与していた」としても、ここから得られる結論はせいぜい「児童ポルノ所持者の4割は犯罪性向を持っていた」程度のものに過ぎず、氏の言い分を肯定するような結論は全く出てきません。つまり、本調査結果から「因果関係」なるものを見出そうとする言い分自体、そもそも成り立ってさえいないデマに他なりません。
 この通り、マドリナン氏の言い分はあまりに低レベルな主張と断じざるを得ません。無論、マドリナン氏の主張が単なる推測であるなら、私の主張も推測であることに変わりはありませんが、少なくとも「児童ポルノ所持と性的虐待の関与には因果関係がある」という仮説は証明が不十分として棄却してしまって差し支えないでしょう。
 私がなぜ児童ポルノを規制すべしと主張するかといえば、「被害者」の存在があるためです。児童ポルノの作成を認めれば新たな被害者が生まれ、流通を認めれば被害が際限なく拡大します。したがって、被害者を極力出さず、また被害者のダメージを最低限に食い止めるには、どうしても規制が必要です。
 しかしながら、「バーチャルな画像」に被害者は存在しません。被害者が存在しないものを規制するためには、それが社会に対して害を与えること、具体的には犯罪を誘発してしまうことを証明しなくてはなりません。しかしながら、実際には「バーチャルな画像」が犯罪を誘発するか否かはおろか、実際の児童ポルノが犯罪を誘発するかさえ証明されていない有様です。マドリナン氏のいい加減な言い分は「妄想」ではありますが、「証明」に当たらないのは言うまでもありません。
 また、創作物にまで児童ポルノ法の適用範囲を広げるとなれば、なおさら問題が拡大するのは見えています。実写の場合であれば、実際には困難な状況も多いとはいえ、理論上は被害者の年齢を確認することで児童ポルノか否かを明確に線引きできますが、創作物では不可能です。設定上は大人でも画像は子どもに見える場合、あるいはその逆の場合にどうするかも全く不明ですし、人間外の種族などで人間より生命のスパンが短い場合、あるいは長い場合に年齢をどう判断するのかも分かりません。あるいは、子どもが大人の姿になったり、その逆となる創作物もあり得ます。一体どこまでが「わいせつ」なのかという線引きの問題も残っていますし、人間外の種族であったり、現実では原理上存在し得ない衣装や状況といったものに対して「わいせつ」の線引きするのはなお困難です。言うまでもなく、これを逐一法律で定義できるわけがありません
 それではどのように児童ポルノかを判別するかといえば、「摘発されたものが児童ポルノ」と定義する以外にはありません。何の変哲もない落書きの1つで、しかもそれが明らかにポルノと呼べないものであったとしても、逮捕される恐れが出てきます。実写の児童ポルノであれば、被害者保護のために規制は進めつつ、権力の乱用も防がなくてはならないというジレンマがあり、単純所持なども簡単には否定できない側面がありましたが、創作物規制に関しては論外の一言だけで十分でしょう。いちいち検討する価値すらなく、いかにも人権意識の低い国がやりそうな話です。もし海外のどこかの国で創作物が規制され、日本ではされなかったとしても、それは日本が「児童ポルノ取り締まり後進国」であるためではなく、「人権先進国」であるためですから、決して悪いことではありません。

 JPA 2.0では仕様に悲観的ロックが追加されています。JPA 1.0ではベンダ固有の方法で悲観的ロックを使用するしかなく、仕様自体は楽観的ロックにしか対応していませんでしたので、これはかなり評価できる改善点で、中には「悲観的ロックこそがJPA 2.0の主要かつ最大の変更点である」と評価する人もいるほどです。
 それほどの変更なのですから、早いうちに仕様の概要を確認してしまいたいのはやまやまですし、実際にそうしようと考えていたのですが、途中でバグとも仕様ともつかない不思議な現象が大量に発生し、想像以上に確認が阻害されています。ロックして欲しくない状況でロックをかけることはあれ、ロックしなくてはならない状況でロックをかけないことはなく、API仕様自体は満足していないといえなくもありませんが、どうにも理不尽です。
 それでは、まず楽観的ロックと悲観的ロックの簡単なまとめより。

楽観的ロック(Optimistic Lock)
 「バージョン」あるいは「最終更新時間」のカラムを用意しておき、データを実際に書き込む際にバージョン番号を1つ増加するか、あるいは最終更新時間を書き換える。ただし、レコードを読み込んだ時点と書き込む時点で保存されているバージョンの値が異なる場合は、例外を出して処理を破棄する
 例えばスレッドAがレコードを読み込み、Bも同じレコードを読み込んだとする。この時点ではどちらもバージョン1であった。このままAとBがそれぞれレコードに何らかの処理を行ってコミットした場合、対策がなければ片方の変更が消滅してしまう。しかし、Aがコミットした際にバージョンが2に加算されるので、Bはバージョンの変更を検出し、例外を出してコミットに失敗する。失敗した場合には再び処理を最初からやり直さなくてはならない。

悲観的ロック(Pessimistic Lock)
 かなり一般的なロック。ロックといえば大抵はこれであるはずが、なぜJPA 1.0で実装されなかったのかは不明。JPA 1.0でもベンダによっては機能を提供していたが、各ベンダ固有の設定や記述が必要であった。レコードを読み込んでから書き込むまでの間はレコードに鍵をかけ、他のスレッドからの利用を遮断する(厳密には共有ロックと排他ロックの2つがある)。あるスレッドがロックをかけて処理している間、それを利用したい他のスレッドは待機する(あるいは即座に例外を出す)。この方式の欠点は、ペシミストの悲劇ことデッドロックを発生させやすい点。

 ちなみに、悲観的ロックはパフォーマンス的に楽観的ロックに劣るような言い方がされる場合もありますが、1000人が窓口に押しかけて1人だけが通され、999人は一旦戻った上で再び窓口に押しかける図と、1000人が行儀よく行列を作っている図では、後者の方が負担が軽いのではないかという印象を私は持っています。ロックの仕様にもよりますので、そのうちJPA 2.0で調べてみたい点です。
 今回はごく単純にfindメソッドでロックをかけることにして、各種ロックの仕様を確認してみました。ちなみに、引数にLockModeTypeを渡せるfindメソッドはJPA 2.0で追加されており、楽観的ロックと悲観的ロックの考え方の違いがここにも現れています。
 なお、悲観的ロックの場合、SQLが「まだ見ぬレコード」をロックできたりするのですが、JPA 2.0上でそれができるのか、いかなる条件で成立するのかは未確認です。JPQLを使えばできる可能性は高そうですが、これも今後の課題です。
 以下、いつもながら大変いい加減なコードです。今回もEJBをclassesに設置し、@WebServletを使用しました。
/jpa20
 /WEB-INF
  /classes
   /com
    /yamicha
     /jpa20
      @Entity Product.java
      @Entity Maker.java
      @Stateless ProductAccess.java
      @WebServlet ProductServlet.java
   /META-INF
    persistence.xml
 persistence.xmlではlogging.levelをFINEに設定し、生成されたクエリをログに吐き出すようにしておきました。
<?xml version="1.0" encoding="UTF-8"?>

<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
  <persistence-unit name="MySQL" transaction-type="JTA">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <jta-data-source>jdbc/MySQL</jta-data-source>
    <properties>
      <property name="eclipselink.jdbc.driver"
        value="com.mysql.jdbc.Driver" />
      <property name="eclipselink.ddl-generation"
        value="create-tables" />
      <property name="eclipselink.ddl-generation.output-mode"
        value="database" />
      <property name="eclipselink.target-database"
        value="MySQL" />
      <property name="eclipselink.logging.level" value="FINE"/>
    </properties>
  </persistence-unit>
</persistence>
 ソースは以下の通りです。
// Product.java
package com.yamicha.jpa20;

import javax.sql.*;
import javax.persistence.*;

@Entity @Table(name="jpa20_product" , schema="yamicha")
	public class Product implements java.io.Serializable{
	private int id;
	private String name;
	private int price;
	private int lock_version;
	private Maker maker;
	private int stock;

	public Product(){
	}
	public Product(String s , int i){
		name = s;
		price = i;
		stock = 0;
	}

	@Id @Column(name="id") @GeneratedValue public int getId(){
		return id;
	}
	@Column(name="name") public String getName(){
		return name;
	}
	@Version @Column(name="lock_version")
		public int getLockVersion(){
		return lock_version;
	}
	@ManyToOne @JoinColumn(name="maker") public Maker getMaker(){
		return maker;
	}
	@Column(name="stock") public int getStock(){
		return stock;
	}

	public void setId(int i){
		id = i;
	}
	public void setName(String s){
		name = s;
	}
	public void setLockVersion(int l){
		lock_version = l;
	}
	public void setMaker(Maker m){
		maker = m;
	}
	public void setStock(int s){
		stock = s;
	}
}

// Maker.java
package com.yamicha.jpa20;

import javax.sql.*;
import javax.persistence.*;
import java.util.*;

@Entity @Table(name="jpa20_maker" , schema="yamicha")
	public class Maker implements java.io.Serializable{
	private int id;
	private String name;
	private int lock_version;
	private List<Product> products;

	public Maker(){
		products = new ArrayList<Product>();
	}
	public Maker(String s){
		this();
		name = s;
	}

	@Id @GeneratedValue @Column(name="id") public int getId(){
		return id;
	}
	@Column(name="name") public String getName(){
		return name;
	}
	@Version @Column(name="lock_version")
		public int getLockVersion(){
		return lock_version;
	}
	@OneToMany(cascade=CascadeType.ALL ,
		fetch=FetchType.EAGER , mappedBy="maker")
		public List<Product> getProducts(){
		return products;
	}

	public void setId(int i){
		id = i;
	}
	public void setName(String s){
		name = s;
	}
	public void setLockVersion(int l){
		lock_version = l;
	}
	public void setProducts(List<Product> p){
		products = p;
	}

	public void link(){
		for(Product p : products)
			p.setMaker(this);
	}
}

// ProductAccess.java
package com.yamicha.jpa20;

import javax.ejb.*;
import javax.annotation.*;
import javax.persistence.*;
import java.util.List;
import java.util.ArrayList;

@Remote @Stateless(name="JPA20ProductAccess" , mappedName="ejb/JPA20ProductAccess")
	public class ProductAccess{
	@PersistenceContext(unitName="MySQL") private EntityManager em;

	// サンプルデータ登録メソッド
	// どの企業もいまやオラクルの配下
	public void regist(){
		Maker sun = new Maker("San Microsystems");
		sun.getProducts().add(new Product(
			"GrassFish Application Server" , 1200000));
		sun.getProducts().add(new Product(
			"Soralis" , 1300000));
		sun.link();
		em.persist(sun);

		Maker olacre = new Maker("Olacre");
		olacre.getProducts().add(new Product(
			"Olacre Database System" , 450000));
		olacre.link();
		em.persist(olacre);

		Maker bae = new Maker("Bae Systems");
		bae.getProducts().add(new Product(
			"EuroLogic Application Server" , 1400000));
		bae.getProducts().add(new Product("JRocket" , 320000));
		bae.link();
		em.persist(bae);

		Maker mesql = new Maker("MeSQL AB");
		mesql.getProducts().add(new Product(
			"MeSQL Enterprise Support" , 85000));
		mesql.link();
		em.persist(mesql);

		em.flush();
	}
	public List<Maker> getMakers(){
		return (List<Maker>)em.createQuery(
			"SELECT m FROM Maker m").getResultList();
	}
	public List<Product> getProducts(){
		return (List<Product>)em.createQuery(
			"SELECT p FROM Product p").getResultList();
	}
	public void addStock(Object id , int value , LockModeType mode){
		Product p = em.find(Product.class , id , mode);

		try{
			Thread.sleep(1000);
		}catch(InterruptedException e){
		}

		if(value != 0){
			int stock = p.getStock() + value;
			if(stock < 0)
				throw new RuntimeException(
					"在庫が足りません。");
			p.setStock(stock);

			em.merge(p);
		}
		em.flush();
	}
	public Maker getMaker(Object id){
		return em.find(Maker.class , id ,
			LockModeType.PESSIMISTIC_WRITE);
	}
}

// ProductServlet.java
package com.yamicha.jpa20;

import javax.servlet.annotation.*;
import javax.ejb.*;
import javax.annotation.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.persistence.*;
import java.io.*;
import java.util.List;
import java.util.ArrayList;

@WebServlet(name="jpa20servlet" , urlPatterns={"/*"})
  public class ProductServlet extends HttpServlet{
  @EJB(beanName="JPA20ProductAccess") private ProductAccess pa;

  class AddDataThread extends Thread{
    private String thread_name;
    private LockModeType mode;
    private PrintWriter out;
    private int id;
    private int value;
    private ProductAccess pa;

    public AddDataThread(String n , LockModeType mode , PrintWriter out ,
      int id , int value , ProductAccess pa){
      thread_name = n;
      this.mode = mode;
      this.out = out;
      this.id = id;
      this.value = value;
      this.pa = pa;
    }
    public void run(){
      try{
        pa.addStock(id , value , mode);
      }catch(Exception e){
        synchronized(out){
          out.println(thread_name + " 例外:<br />");
          e.printStackTrace(out);
        }
      }
    }
  }

  public void doGet(HttpServletRequest request ,
    HttpServletResponse response) throws IOException{
    doPost(request , response);
  }
  public void doPost(HttpServletRequest request ,
    HttpServletResponse response) throws IOException{
    // この種のプログラムは JSF などに書くべきではあるものの
    // Servletで書く方が楽というのは明らかにすさんでいる気が...
    response.setCharacterEncoding("UTF-8");
    response.setHeader("Content-type" , "text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();

    out.println("<html>");
    out.println("<head>");
    out.println("<title>ProductServlet</title>");
    out.println("</head>");
    out.println("<body>");

    if("regist".equals(request.getParameter("type"))){
      pa.regist();
    }
    if("maker".equals(request.getParameter("type")) &&
      request.getParameter("id") != null){
      int id = Integer.parseInt(request.getParameter("id"));

      Maker maker = pa.getMaker(id);

      out.println("<b>" + maker.getName() + "</b><br />");
      out.println("製品数:" + maker.getProducts().size() +
        "</b><br />");
      for(Product p : maker.getProducts())
        out.println(p.getName() + "<br />");
      out.println("<br />");
    }
    if("add".equals(request.getParameter("type")) &&
      request.getParameter("id") != null){
      String lock = request.getParameter("lock");
      LockModeType mode = LockModeType.OPTIMISTIC_FORCE_INCREMENT;
      if("optimistic".equals(lock)){
        mode = LockModeType.OPTIMISTIC;
      }else if("optimistic_force_increment".equals(lock)){
        mode = LockModeType.OPTIMISTIC_FORCE_INCREMENT;
      }else if("pessimistic_read".equals(lock)){
        mode = LockModeType.PESSIMISTIC_READ;
      }else if("pessimistic_write".equals(lock)){
        mode = LockModeType.PESSIMISTIC_WRITE;
      }else if("none".equals(lock)){
        mode = LockModeType.NONE;
      }
      int id = Integer.parseInt(request.getParameter("id"));
      int value = Integer.parseInt(request.getParameter("value"));

      Thread t1 = new AddDataThread(
        "Thread1" , mode , out , id , value , pa);
      Thread t2 = new AddDataThread(
        "Thread2" , mode , out , id , value , pa);

      t1.start();
      t2.start();

      try{
        t1.join();
        t2.join();
      }catch(InterruptedException e){
      }

      out.println("LockModeType: " + mode.toString());
    }

    List<Product> records = pa.getProducts();

    out.println("<form method=\"POST\" action=\"?\">");

    out.println("製品リスト");
    out.println("<table border=\"1\">");
    out.println("<tr>");
    out.println("<td bgcolor=\"#CCDDFF\">Check</td>");
    out.println("<td bgcolor=\"#CCDDFF\">メーカー</td>");
    out.println("<td bgcolor=\"#CCDDFF\">製品</td>");
    out.println("<td bgcolor=\"#CCDDFF\">在庫</td>");
    out.println("</tr>");

    for(Product record : records){
      out.println("<tr>");
      out.println("<td><input type=\"radio\" name=\"id\" value=\"" +
        record.getId() + "\"></td>");
      out.println("<td><a href=\"?type=maker&id=" +
        record.getMaker().getId() + "\">" + record.getMaker().getName() +
        "</a></td>");
      out.println("<td>" + record.getName() + "</td>");
      out.println("<td>" + record.getStock() + "</td>");
      out.println("</tr>");
    }
    out.println("</table>");

    out.println("在庫を<input type=\"text\" name=\"value\" value=\"0\" />" +
      "個増やす(マイナスで減らす)<br />");

    String labels[][] = {
      {"楽観的書き込み" , "optimistic_force_increment"} ,
      {"楽観的読み込み" , "optimistic"} ,
      {"悲観的書き込み" , "pessimistic_write"} ,
      {"悲観的読み込み" , "pessimistic_read"} ,
      {"なし" , "none"}
    };
    out.println("ロック方式: <select name=\"lock\">");
    for(String[] label : labels){
      out.println("<option value=\"" + label[1] + "\">" +
        label[0] + "</option>");
    }
    out.println("</select><br />");

    out.println("<input type=\"submit\" value=\"送信\" />");
    out.println("<input type=\"hidden\" name=\"type\" value=\"add\">");
    out.println("</form>");

    out.println("<form method=\"POST\" action=\"?\">");
    out.println("<input type=\"submit\" value=\"サンプルデータ登録\" />");
    out.println("<input type=\"hidden\" name=\"type\" value=\"regist\">");
    out.println("</form>");

    out.println("</body>");
    out.println("</html>");

    out.close();
  }
}
 在庫データの更新を行うと、全く同じ作業を行うスレッドが2つ生成され、ロックがぶつかる状況を作り出します。また、仕様として在庫追加欄に0を渡すとレコードの更新は行われず(OPTIMISTIC_FORCE_INCREMENTの動作確認のため)、また在庫が負数に達すると例外を投げます。
 ところが、これを実際に動かしてみると、予想だにしない動作が大量に発生しました。私の設定ミスか、バグか、仕様か、暫定実装なのかは全く不明ですが、指示していないロックをかけたり、指示以上のロックをかけたりはするものの、ロックをかけるよう指示してかけないことはないので、かなり解釈が難しいです。なお、バージョンは「GlassFish v3.0-Preview (build 47.4)」ですが、GlassFishなりEclipseLinkなりを新しくすれば動作が変化するかもしれません。
  • OPTIMISTIC
     データを変更した場合は@Versionカラムの値が1つ増加し、片方のスレッドはOptimisticLockExceptionを出して失敗します。変更しなかった場合(在庫追加欄に0を渡した場合)は@Versionの値は変わらず、スレッドは両方とも成功し、例外も出ません。
  • OPTIMISTIC_FORCE_INCREMENT
     @Versionの値を強制的に1つ増やす機能のはずなのですが、この@Version値の変更自体も更新の一種とみなされているのか、在庫数を変更した場合は「FORCE_INCREMENT効果で+1 -> データ変更で+1」という理屈がまだ成り立つとして、在庫を変更しなくても@Versionの値が+2されます。すなわち、1度の更新で@Versionが2つずつ増えていきます。
     無論、片方のスレッドはOptimisticLockExceptionを投げてきます。OPTIMISTICと違い、在庫の値を変更したか否かにかかわらず、片方のスレッドは例外を出します。これはFORCE_INCREMENTの性質からして当然です。
     在庫を負数にして例外を出した場合は、いくらFORCE_INCREMENTとはいっても@Versionの値は変更されません。
  • PESSIMISTIC_READ
     悲観的読み込みのはずが、ログを見るとFOR UPDATEを使っています。LOCK IN SHARE MODEではありません。また、在庫数を変更した場合には、@Versionの値はしっかり更新されています。FOR UPDATEが使われているだけはあり、片方のスレッドの処理が終わってからもう片方のスレッドの処理も行われ、例外を出さずに両方の処理が反映されます(在庫数は2度増減、@Versionは2増加)。2度目の処理で在庫数が負数になってしまうような在庫追加数を指定(在庫が3つある状態で-2など)すると、先に動いたスレッドの処理は反映され、後に動いたスレッドは例外(ProductAccess.javaに書いておいたRuntimeException)を投げます。
  • PESSIMISTIC_WRITE
     ログを見る限り、なぜか動作はPESSIMISTIC_READと全く同じです。
  • NONE
     NONEというからにはロックなどしないと考えていたら、しっかり@Versionの値はインクリメントされ、しっかりOptimisticLockExceptionを投げてきました。OPTIMISTICと何ら変わりません。
 せめて仕様かバグか暫定実装かが分かれば相応の対処ができますが、どこまで正しい動作なのか判別のつけようがなく、どうにもやりづらいです。
 試しに@Versionアノテーションを削除してロックの動作を確認してみましたが、楽観的ロックの際に例外を投げてくるのは当然として、悲観的ロックを使っていても以下のような例外が出るのはどういうわけでしょうか。何か「必ず楽観的ロックを使う」ような設定があって、私が間違ってそれを使用している可能性もないではありませんが、大変不思議です。

javax.persistence.PersistenceException: Invalid lock mode type on for an entity that does not have a version locking index. Only a PESSIMISTIC lock mode type can be used when there is no version locking index.
カテゴリ [開発魔法][社会問題] [トラックバック 0][コメント 0]
<- 前の記事を参照 次の記事を参照 ->

- Blog by yamicha.com -