やっぱり sql を書きたい派に送る jooq
TRANSCRIPT
やっぱり SQL を
書きたい派に送る jOOQ第九回 #渋谷java on 2014-12-13
Yusuke Ikeda(@yukung)
$ whoami• 池田 裕介
• @yukung : http://yukung.hatenablog.com/
• 道玄坂の方の緑の会社でジャバとか Node.js とか
Cassandra とか
• 会社では golang 流行ってて乗り遅れ…
• Groovy とか Gradle がすきです
本題
jOOQ ご存じですか
ちょっとした案件にて• とあるマスタ情報を返す Web API
• マスタメンテナンスのバッチ
• 期間1ヶ月程度であんまり時間ない
• 自分1人だけ
• 作ったら引き継ぎする予定で、性質的に頻繁にメンテされるものではない
弊社 Java プロジェクトの鉄板構成
• Spring Framework
• Tomcat
• MyBatis 3
• MySQL
(´ヘ`;)ウーム…
まぁ引き継ぎ考えたら堅いけどね…
でもぶっちゃけ
(゚⊿゚)ツマンネモチベーション大事ですよね…?
というわけで
軸はあまり変えずに今っぽく
• Spring Framework
• Tomcat
• MyBatis 3
• MySQL
軸はあまり変えずに今っぽく
• Spring Framework
• Tomcat
• MyBatis 3
• MySQL
• Spring Boot
• Dropwizard と迷ったけど、ちょっと停滞気味?
• (Embedded Tomcat/Jetty)
• ???
• MySQL
みなさんなら???のとこ何選びますか?
• そのまま MyBatis でええやん派
• そのまま MyBatis でええやん派
• 後述
• そのまま MyBatis でええやん派
• 後述
• JPA だろ標準だし jk 派
• そのまま MyBatis でええやん派
• 後述
• JPA だろ標準だし jk 派
• JPA/Hibernate はハマった話の方がよく聞く
• そのまま MyBatis でええやん派
• 後述
• JPA だろ標準だし jk 派
• JPA/Hibernate はハマった話の方がよく聞く
• ここはビズリーチだぞ DBFlute だろ空気読めよ
• Doma 一択派
• Doma 一択派
• Doma 良かったんだけど、S2Dao 経験してる人居なくて決め手に欠けるのと、Lombok と相性悪くて諦めた(APT 絡みで)
• Doma 一択派
• Doma 良かったんだけど、S2Dao 経験してる人居なくて決め手に欠けるのと、Lombok と相性悪くて諦めた(APT 絡みで)
• 同じ理由で QueryDSL も諦めた
知見
https://twitter.com/gakuzzzz/status/443924561961037824https://twitter.com/gakuzzzz/status/443925068486164480
知見
https://twitter.com/gakuzzzz/status/443382960935284736
先日 @cero_t さんがこんな Tweet を
http://d.hatena.ne.jp/cero-t/20141212/1418339302
なんという昨日の俺
やっぱり SQL
書きたい派
あと私 MyBatis が
好きじゃない
MyBatis で 実際に見たコード
@Select({ "SELECT ", "isbn,", "author_id,", "title,", "publish_date,", "create_date,", "update_date ", "FROM ", "book ", "WHERE ", "author_id = #{authorId} ", "AND ", "#{publishDate} = publish_date" }) Book select(@Param("authorId") long authorId, @Param("publishDate") Date publishDate);
MyBatis で 実際に見たコード
@Select({ "SELECT ", "isbn,", "author_id,", "title,", "publish_date,", "create_date,", "update_date ", "FROM ", "book ", "WHERE ", "author_id = #{authorId} ", "AND ", "#{publishDate} = publish_date" }) Book select(@Param("authorId") long authorId, @Param("publishDate") Date publishDate);
• 保存したら崩れたんだけど…フォーマッタどこや • 修正入れてテスト実行した時に SQLException とかよく見る • 調査するのにクエリコピペするのめんどくさすぎる
クエリビルダーあるよ
public String selectPersonLike(final String id, final String firstName, final String lastName) { return new SQL() {{ SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FIRST_NAME, P.LAST_NAME"); FROM("PERSON P"); if (id != null) { WHERE("P.ID like ${id}"); } if (firstName != null) { WHERE("P.FIRST_NAME like ${firstName}"); } if (lastName != null) { WHERE("P.LAST_NAME like ${lastName}"); } ORDER_BY("P.LAST_NAME"); }}.toString();
クエリビルダーあるよ
public String selectPersonLike(final String id, final String firstName, final String lastName) { return new SQL() {{ SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FIRST_NAME, P.LAST_NAME"); FROM("PERSON P"); if (id != null) { WHERE("P.ID like ${id}"); } if (firstName != null) { WHERE("P.FIRST_NAME like ${firstName}"); } if (lastName != null) { WHERE("P.LAST_NAME like ${lastName}"); } ORDER_BY("P.LAST_NAME"); }}.toString(); • 多少マシになったけど相変わらずカラム指定は文字列
• ぶっちゃけ StringBuilder とあんまり違わない…?
MyBatis Generator があるじゃない!
MyBatis Generator で 生成した Criteria 使うと
public List<ItemAchievement> findAllByApplicationId(long UserId, long ApplicationId){ ItemAchievementExample ItemAchievementExample = new ItemAchievementExample(); ItemAchievementExample.Criteria criteria = ItemAchievementExample.createCriteria(); criteria.andApplicationIdEqualTo(ApplicationId).andUserIdEqualTo(UserId); String tableSuffix = RoutingPartitionManager.getPartition(UserId).getSuffix(); return ItemAchievementMapper.selectByExample(tableSuffix, ItemAchievementExample); }
MyBatis Generator で 生成した Criteria 使うと
public List<ItemAchievement> findAllByApplicationId(long UserId, long ApplicationId){ ItemAchievementExample ItemAchievementExample = new ItemAchievementExample(); ItemAchievementExample.Criteria criteria = ItemAchievementExample.createCriteria(); criteria.andApplicationIdEqualTo(ApplicationId).andUserIdEqualTo(UserId); String tableSuffix = RoutingPartitionManager.getPartition(UserId).getSuffix(); return ItemAchievementMapper.selectByExample(tableSuffix, ItemAchievementExample); }
• Example って何や…サンプルっぽいな( ゚д゚)ポカーン • ぱっと見なにしてるか分かりづらい • メソッド名がウソついてたりしてたら…ウッ
うーむ。悩ましい。
そこで jOOQ
http://www.jooq.org/
特徴• Database First
• 既存の DB スキーマを重視します(レガシーなシステムのスキーマにも対応)
• Typesafe SQL / Fluent API
• コードを見ると一目瞭然。とことんタイプセーフにしようとする意思を感じます。
• Code Generation
• APT ではなく生成ツールを Maven / Gradle などから使います
• Active Records
• POJO や JPA ベースの Entity、イミュータブルな POJO や DAO も設定により自動生成
• and more…
サンプル
-- Select all books by authors born after 1920, -- named "Paulo" from a catalogue: SELECT * FROM author a JOIN book b ON a.id = b.author_id WHERE a.year_of_birth > 1920 AND a.first_name = 'Paulo' ORDER BY b.title
http://www.jooq.org/doc/3.5/manual/sql-building/sql-statements/dsl-and-non-dsl/
サンプル
titleResult<Record> result = create.select() .from(AUTHOR.as("a")) .join(BOOK.as("b")).on(a.ID.equal(b.AUTHOR_ID)) .where(a.YEAR_OF_BIRTH.greaterThan(1920) .and(a.FIRST_NAME.equal("Paulo"))) .orderBy(b.TITLE) .fetch(); !
http://www.jooq.org/doc/3.5/manual/sql-building/sql-statements/dsl-and-non-dsl/
初見の感想• 日本ではほぼ聞かないけど、海外では割と使われている印象
• ドキュメントがかなりしっかりしている(英語)
• jOOQ ユーザーの Screen Cast やブログも結構ある
• Screen Cast 一度見れば使い方はほぼ分かるので、後はドキュメント見つつ
• Stack Overflow にも良QAがたくさん
• ただし答えてるのが作者の Lukas Eder さんばっかりですごい頑張ってる感
初見の感想• GitHub にも連携ライブラリとのサンプルなど結構ある
• 開発も割と活発(ただしやっぱり Lukas Eder さんだけ頑張ってる感)
• 有料サポートもあるみたい
• Lukas Eder さんは他にも jOOL とか jOOX とか作ってて DSL 厨っぽい
Demohttps://github.com/yukung/jooq-sample
使ってリリースした後の知見!
• リリースした後しばらく経ってから Repository 層(Dao 層)のコード見ても、SQL 読めれば大体何やってるかはすぐ分かる
• POJO とのマッピングは .fetchInto(POJO.class) でわりかし空気読んでマッピングしてくれる
• カラム名とフィールド名が対応してない POJO や Join を考慮した POJO にマッピングしたかったら ModelMapper というマッピングライブラリと連携できるのでそれを使う
• Flyway とか Liquibase のようなマイグレーションツールと組合せてコード生成しながら使うのがベストプラクティスっぽい
必須じゃないけど便利
// Fetch books and format them as CSV String csv = create.selectFrom(BOOK).fetch().formatCSV(); !ID,AUTHOR_ID,TITLE 1,1,1984 2,1,Animal Farm !// Fetch books and format them as JSON String json = create.selectFrom(BOOK).fetch().formatJSON(); !{"fields":[{"name":"field-1","type":"type-1"}, {"name":"field-2","type":"type-2"}, ..., {"name":"field-n","type":"type-n"}], "records":[[value-1-1,value-1-2,...,value-1-n], [value-2-1,value-2-2,...,value-2-n]]}
未知数なところ• 扱った案件が小規模だったので、パフォーマンスとか気になるところ
• たくさん Join しなきゃいけないようなクエリ投げるプロジェクトで使うとやっぱりカオスる気がする
• これは多分何かで解決するより、テーブルを見直しましょうだと思う
• マッピングや結果セットを Handler や Listener 使ってコールバック内に処理書けたりするけど、やりすぎるとカオスになる気が
• Generation Tool も大分カスタマイズできるけど、やり過ぎない方が良さそう
• Lukas さんがコミットできなくなったらちとアレかも(一応企業としてやってるみたいだけども)
Conclusion• Java の O/R マッパー色々あるけど、jOOQ もなかなかいいよ!
• とにかく SQL をタイプセーフに書きたいんだ、IDE の補完使ってガシガシ書きたいんだって人にオススメ
• カスタマイズもかなりできるけど、やり過ぎない方がいいと思います
jOOQ も選択肢に入れてみては?
ありがとう ございました