そいや,こっち戻ってきてもう5年か
東京での5年間は長かったが,仙台の5年はあっちゅう間だの。年のせいか?
KeyCastr
タイプしたキーをGrowlみたいなフローティングウィンドウに表示するツール。NetBeans TVとかでたまに見かけるアレ。
スクリーンキャストとかプレゼンするのに役立ちそう。って,どっちもそんな機会ないけど。:-)
アップロードしたファイルをDBに格納してみる
えー,バカのひとつ覚えで「アップロードしたファイルはディスクのどっかに置いとけ」っての信じてきたんだけど,運用が楽じゃない。Grailsとは言え,のちのちWARで配布とか考えると「カレントディレクトリってどこやん?」ってとこから考え込むハメになる。
ならばよろしい,DBに格納してしまえ。ってんで,やってみた。
これがまた至極簡単。まずは,こんなドメイン作るよ。
用もないのにいちいちBLOBデータをフェッチされてはたまらんので,FileInfoとFileContentをわけてみたよ。図にするとこんなん。Blogってのは,シャレだ。
ファイルのアップロード(BlogController)とダウンロード(FileInfoController)の処理はこんな感じ。ビュー側はほとんどDynamic Scaffoldでまかなえるけど,views/blog/create.gsp だけ一部修正するんで生成しといたほうがいい(どっちか言えば,generate-viewsで全部作って create.gsp 以外を消すってのが正しい)。
ポイントは,こんな感じ。
- g:uploadformってタグがあったってこと。
- one-to-oneの場合,addToXXX()に相当する処理がわからんかったんで,普通に代入した(個別に save() しとかないとダメっぽかった)。
- MimeUtilityはJavaMailに含まれているのを使いたかった(準備の手間を惜しんだわけだ)。
とかくGORMでのBLOBの操作はおどろくほど簡単。効率とか消費リソースとかいったらキリないけど,こんな手間要らずでDBにファイルを格納できるんだったら,DBに格納してもいいかなって思うわな。:-)
GORMでjoin fetchをやってみる
さっきの例でBlogController.listを実行すると,こんなSQL(HQL?)が実行される。
Hibernate: select top ? this_.id as id0_0_, this_.version as version0_0_, this_.body as body0_0_, this_.title as title0_0_, this_.uploaded_file_id as uploaded5_0_0_ from blog this_ Hibernate: select fileinfo0_.id as id2_0_, fileinfo0_.version as version2_0_, fileinfo0_.content_type as content3_2_0_, fileinfo0_.file_content_id as file4_2_0_, fileinfo0_.filename as filename2_0_ from file_info fileinfo0_ where fileinfo0_.id=?
1件しかデータ登録してないけど,みごとにN+1問題が起きてるのがわかるね。これをなんとかしてみる。
ぱっと思いついたのが Blogドメインの mapping で eager fetch を明示する事。つまり,こんなん。
class Blog { : static mapping = { uploadedFile lazy:false } // または //static fetchMode = [ uploadedFile: 'eager' ] }
いやいや,ちょっとまて。すでに lazy fetch してないから,これは意味無いだろう(実際,変化なかったし)。というわけで,次に試したのが GORMのfinder に fetchオプションを与える方法。
class BlogController { def scaffold = true def list = { [blogInstanceList: Blog.findAll([fetch: [uploadedFile: 'eager']])] } :
すると,こんな感じに join fetch になる(指定は eager なのにね)。
Hibernate: select this_.id as id2_1_, this_.version as version2_1_, this_.body as body2_1_, this_.title as title2_1_, this_.uploaded_file_id as uploaded5_2_1_, fileinfo2_.id as id0_0_, fileinfo2_.version as version0_0_, fileinfo2_.content_type as content3_0_0_, fileinfo2_.file_content_id as file4_0_0_, fileinfo2_.filename as filename0_0_ from blog this_ left outer join file_info fileinfo2_ on this_.uploaded_file_id=fileinfo2_.id
いちいち finder に fetchオプションを与えるのがうっとうしいけど,これにてN+1問題は回避できたよ。
ちなみにこれ,Grails 1.0.4の話な。
わざわざバージョンの話をするのは,Grails 1.1だとこんな指定方法があるからよ。
class Blog { : static mapping = { uploadedFile fetch:'join' } }
これだと,finderにへんなオプション付けなくても,このようにjoin fetchが実行される。
Hibernate: select top ? this_.id as id0_1_, this_.version as version0_1_, this_.body as body0_1_, this_.title as title0_1_, this_.uploaded_file_id as uploaded5_0_1_, fileinfo2_.id as id1_0_, fileinfo2_.version as version1_0_, fileinfo2_.content_type as content3_1_0_, fileinfo2_.file_content_id as file4_1_0_, fileinfo2_.filename as filename1_0_ from blog this_ left outer join file_info fileinfo2_ on this_.uploaded_file_id=fileinfo2_.id
むむぅ,このためだけでも Grails 1.1 にスイッチする価値はあるな。
ps.
ちょっと前に「いまいまさのぶ on Twitter: "なるほど。GORMでもone-to-oneはLazy fetchしてくれんのか。"」なんて毒電波垂れ流してたんだけど,あらためて試してみたら,そんなことなかった。オレは夢でもみていたのか?
そんときは one-to-one ではなく,one-to-many にして,実態は one-to-one にしてやり過ごした。...って勝手に思い込んで悦に至ってたが,なんか勘違いしてたみたい。:-(
#忘れてしまうには惜しいので,あえて恥をさらしておく。
なんにせよ,ちょっと足を踏み入れるととたんにHibernateの知識が必要になるんだなってのが,垣間みれたのは収穫だった。