Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ

6,968 views

Published on

JJUG-CCC 日本Javaユーザーズグループクロスコミュニティカンファレンス 2018/12 登壇資料です。

Published in: Technology

ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ

  1. 1. #ccc_a1 ツール比較しながら語る O/RマッパーとDBマイグレーション JJUG-CCC 2018 Fall 日本Javaユーザーズグループ クロスコミュニティカンファレンス ベルサール西新宿 2018-12-15 Y.Watanabe
  2. 2. #ccc_a1 (ストップウォッチ スタート確認)
  3. 3. #ccc_a1 Who? ● 渡辺 祐 ● (株)ビズリーチ ● SREグループ ○ Site Reliability Engeneering ● twitter: @nabedge ● nabedge@gmail.com 3
  4. 4. #ccc_a1 Software Design 2019 / 1月号に寄稿しました 第2特集 リリースモデルの変更にどう対処する? Javaのバージョン問題に前向きに 取り組む方法 第3章 Javaをバージョンアップしやすくする アイデア 進化に臆さず,そのメリットを 享受するために 4
  5. 5. #ccc_a1 今日、伝えたいこと ● アプリケーションの寿命 < DBの寿命 ● 流れの速さのギャップに、アプリケーションのテク ノロジーで、うまいこと付き合う方法ってなんだろ う? 5
  6. 6. #ccc_a1 さっそくですがアンケート ● MyBatis(iBatis) ● SpringのJdbcTemplate ● Hibernate ● QueryDSL ● jOOQ ● Doma ● DBFlute ● S2JDBC ● Flyway 過去1年であなたが実際に仕事で使ったものは? 6
  7. 7. #ccc_a1 ただし! ● 銀の弾丸は無い ● エンジニアが現場の状況にあわせて ツールをチョイスして運用するしかない 7
  8. 8. #ccc_a1 もくじ1 1. タイプセーフなO/Rマッパーの特徴 2. DBマイグレーションツールとは? 3. Flywayとは? 8
  9. 9. #ccc_a1 もくじ2 4. 開発のスタート地点は? CREATE/ALTER文? ER図? テーブル定義書.xls ? JPAのエンティティクラス.java @Table, @Column 5. 開発用DBはどこにある? ローカルPC? 共有DBサーバ? 9
  10. 10. #ccc_a1 もくじ3 6. O/Rマッパーのソースコード自動生成を どのタイミングでやるか 7. 自動生成したコードをgitに入れるか 8. 自動生成したコードとドメインオブジェ クトのコードを分けるべきか 10
  11. 11. #ccc_a1 もくじ4 9. テストデータをどうやって投入するか 10. 実際に実行されるSQLを見たい 11. RDBMSの独自関数を使いたい 12. テーブル定義書をどう作るか 11
  12. 12. #ccc_a1 もくじ5 13. 複数のO/Rマッパーを同じプロジェクトで使う or乗り換えるためのヒント 12
  13. 13. #ccc_a1 (盛り込み過ぎ...) 13
  14. 14. #ccc_a1 1. モダンなO/Rマッパーの特徴 14
  15. 15. #ccc_a1 ● Hibernate 2001〜 ● Spring-JDBC(JdbcTemplate) 2001〜 ● iBatis/MyBatis 2005〜 ● S2JDBC 2008〜 ● QueryDSL 2008〜 ● DBFlute 2008〜 ● jOOQ 2010〜 テーブル作成済みのDBサー バからメタデータを読み取っ てO/Rマッピング用Javaソー スを自動生成する方式 古い順に並べて超ざっくり分類 SQLを手で埋め込む方式 素人にはおすすめできない(*1,2) 15
  16. 16. #ccc_a1 SpringのJdbcTemplate List<Book> books = jdbcTemplate.query( “SELECT ISBN, TITLE FROM BOOKS” + “ WHERE ISBN = ? ”, new Object[]{“hoge”}, // “?”のところに入れたい引数 new BeanPropertyRowMapper(Book.class) ); 16
  17. 17. #ccc_a1 MyBatis <!-- xmlファイル --> <select id="selectBook" parameterType=”String” resultType="Book"> <![CDATA[ SELECT ISBN, TITLE FROM BOOKS WHERE ISBN = #{isbn} ]]></select> // Javaコード List<Book> books = bookRepository.select(“hoge”); 17
  18. 18. #ccc_a1 いま紹介したのは旧来型O/Rマッパー ● メリット ○ とにかくSQLを手で書かないと気が済まない人 ● デメリット ○ タイプセーフではない ○ BOOKをBOOKSと書いても実行するまで(バグるまで)ミ スに気づけない ※想定しているテーブル名はBOOKです。前のページは わざと間違いを書いています。 18
  19. 19. #ccc_a1 jOOQ(ジュークと読む) //テーブルのメタデータ情報クラス Book book = Tables.book; // SQLを組み立てて実行 List<BookVo> books = dsl .select(book.isbn, book.title) .from(book) .where(book.isbn.eq(“hoge”)) .fetchInto(BookVo.class); // PoJoであれば手作りクラスでも可 タイプセーフ=間違えたらコンパイルエラーでわかる 赤字は自動されたJavaコードを 使っている箇所 19
  20. 20. #ccc_a1 DBFlute List<Book>books = bookBhv.selectEntity( condition -> { condition.query().setBookIsbn_equal("999"); } ); 赤字は自動されたJavaコードを 使っている箇所 20
  21. 21. #ccc_a1 うたぐり深い人へ、本当の話。 1. jOOQはもっと複雑なSQLを組み立てることも可能です。 a. 参考文献 https://docs.google.com/presentation/d/1MvsMo38Bt-2h4b_ZDSSXNSgq_UuweXx9P0HmlbO y8k8 2. 正直に言うと、DBFluteは group by をサポートしていません。 a. そういうことは「外出しSQL」で書く方向。 b. 外出しSQLの結果マッピングや呼び出しコードの自動生成をサポート。 21 割愛
  22. 22. #ccc_a1 QueryDSL ● jOOQと似てる(ので、サンプルコードは省略) ● NoSQLも積極的(Lucene拡張、MongoDB拡張) ● 2016年9月、QueryDSLのメインなコミッターが 「やりきったから、別の仕事やるわ。」 と事実上の開発停滞宣言。*3 ● jOOQ陣営「QueryDSLおつかれ。俺たちはまだやるぜ」宣言。*4 ● 2018年5月 約2年ぶりのバージョンアップ 22
  23. 23. #ccc_a1 いま風なO/Rマッパーの共通項 ● タイプセーフなJavaコーディングでCRUDを書く ○ ミスったらコンパイルエラー ● DBにピッタリ合わせたJavaコードでCRUDを書く ○ DB変更の影響範囲がコンパイルエラーでわかる ● 上記を実現するために、 ○ テーブル作成済みのDBサーバから自動的にメタデータを 読み取って、Javaソースコードを自動生成 23
  24. 24. #ccc_a1 2. DBマイグレーションツールとは? 24
  25. 25. #ccc_a1 ここで言うDBマイグレーションとは DBに対する変更=DDL文の適用=を管理するツール ● O/Rマッパー同梱型 ○ Ruby on Rails ○ DBFlute ○ Hibernate(?) ● 専用ツール型 ○ LiquiBase ○ MyBatis Migration ○ Flyway 25
  26. 26. #ccc_a1 「いまの状態のDB -> 変更のDDLをあてる -> 次の状態のDBになる」 1. 「次の状態のDB」のフルDDL(CREATE文)を手で作っておく 2. 「今の状態から変更するためのDDL」も手で作っておく 3. DBFluteの”save-previous”コマンドで今の状態のDBの定義情報を保存 4. 3と1を使ってDBFluteの”alter-check”コマンドで下記を検証できる 今の状態 + 変更のDDL = 次の状態 5. 4の結果を見たDBAは安心して「変更のDDL」を本番DBで実行 6. 開発者はDBFluteの”replace-schema”で手元の開発DBを再構築 26 DBFluteのマイグレーション機能
  27. 27. #ccc_a1 LiquiBase ● 却下。 ● 巨大なXMLファイルを手でメンテし続ける前提だから 27 割愛
  28. 28. #ccc_a1 MyBatis Migrations ● 時間が無いので割愛。 ● 考え方はFlywayとよく似ている 28 割愛
  29. 29. #ccc_a1 3. Flywayとは (これが近年の本命) 29
  30. 30. #ccc_a1 基本的な考え方 DBマイグレーションツールが無い世界で DBA担当がDBに向かってやる基本動作は 究極、これだけ。 1. DDL文を FooBar-0001.sql ファイルに書いて保存。 2. 順に、一度だけ、実行する。 30
  31. 31. #ccc_a1 DB担当者の基本動作を そのままソース管理&実行管理する ツールが Flyway だと思えばいい 31
  32. 32. #ccc_a1 32 src/main/resources/db/migration/ V1.1__foo_init.sql <- 去年のサービス開始のとき V1.2__hoge_alter.sql <- 先月の機能追加のとき V1.3__add_foobar.sql <- 来週のための機能追加 1. DBに対する変更を.sqlファイルで積み重ねてゆく 2. flywayを実行 $ ./gradlew flywayMigrate
  33. 33. #ccc_a1 33 3. 管理テーブルに無いsqlファイルだけが実行対象となる > SELECT ... FROM SCHEMA_VERSION version | script | success ---------+-----------------------+--------- 0.1 | << Flyway Baseline >> | true 1.1 | V1.1__foo_init.sql | true 1.2 | V1.2__hoge_alter.sql | true 1.3 <- このレコードは未だ無いのでV1.3__add_foobar.sqlが対象 4. sqlファイルの追加や変更がない状態でもう一度 flywayMigrate して も、全て実行済みでSCHEMA_VERSIONに記録されていれば、何も起きな い(べき等性)
  34. 34. #ccc_a1 補足 ● 運用中のDBに、途中から導入することも可能 ○ Flyway ■ “flyway baseline” でググる ○ DBFlute ■ 詳しくはマニュアルを ■ O/Rマッパーとして使わずとも、他のマイグレーション 支援コマンド群だけ使うことが可能 34
  35. 35. #ccc_a1 ちょっと休憩 35 1. 水を飲む 2. 時間を確認 20分くらい?
  36. 36. #ccc_a1 4. 開発のスタート地点はどこ? 36
  37. 37. #ccc_a1 37 A. 手書きのDDL(を積んでゆくだけ) 最初にCREATE TABLE、 運用しながら ALTER, CREATE/DROP INDEX, CREATE/DROP TABLE... B. ER図をまず書く。(そこからDDL文を自動生成) C. JPAのエンティティクラスを手書きし、Hibernate-JPAでDDL文 を自動生成 D. テーブル定義書.xlsと手書きのDDLを同時に書き続ける
  38. 38. #ccc_a1 38 A. 手書きのDDLをテキスト形式で積み上げる この方法以外はすべて、なんだかんだで... ● ツールのセットアップと使い方が難しい ● 引き継ぎが難しくなる ● ツールが有償かつツールにロックインされる ● バージョン管理システムとの相性が...
  39. 39. #ccc_a1 5. 開発DBサーバはどこにあるべき? 39
  40. 40. #ccc_a1 40 A. 共有DB方式 チームのエンジニア全員のPCからサーバ室の1台のDBサー バに接続 B. ローカルDB方式 それぞれのエンジニアのPCに自分専用の開発DBを構築
  41. 41. #ccc_a1 ローカルDB方式 = Docker時代のデファクト 41 $ docker run mysql:5.7 $ ./gradlew flywayMigrate ● 不要なカラムを削除したい ● 不適切な名前のカラムを RENAMEしたい ● 新しい機能のために新しい テーブルを追加したい ● 並行して作業できる ● ただしFlywayの場合はsqlファイルのバージョン番号 だけは衝突しないように話し合う $ docker run mysql:5.7 $ ./gradlew flywayMigrate
  42. 42. #ccc_a1 6. O/Rマッパーのソースコード自動生成を どのタイミングでやるか 7. 自動生成したコードをgitに入れるか 42
  43. 43. #ccc_a1 ローカルDB方式 + 自動生成型O/R + Flyway の場合 1. エンジニアはそれぞれやりたいDB変更をDDLで書く 書いたら手元PCで ./gradlew flywayMigrate (手元のDBが変更される) 2. エンジニアはそれぞれ手元でO/RマッパのJavaコード生成を実行 自動生成したJavaコードはコミット対象外!(理由は後述) 3. 2.に合わせてアプリのJavaコードも書く 4. 手元のPCでアプリを起動 -> 動作確認 5. プルリクを作る -> masterブランチにマージ (続く) 43
  44. 44. #ccc_a1 ※以下はエンジニアのPCではなくCIサーバが実施 6. 全てのソースコードツリーをチェックアウト 7. CIサーバ内部でDockerでローカルDBを起動 8. ./gradlew flywayMigrate (ローカルDBの再構成) 9. ./gradlew [O/RマッパのJavaコード自動生成コマンド] 10. ./gradlew build ->全てのコードがjar/warファイル化される 11. アプリをデプロイする前に ./gradlew flywayMigrate -DdbHost=... ※今度はDBの向き先をデプロイ先環境内のDBにしておく 12. jar/warをデプロイ 44
  45. 45. #ccc_a1 前頁のポイント ● DB変更とアプリケーションコードの変更を 同じブランチ/プルリクエストで作業できる ○ FlywayのマイグレーションSQLがバッティングしないように、変更内容 と適用順序をエンジニア間で要調整 ● O/Rマッパーの自動生成Javaコードはgitコミットしない ● そのかわりに開発者のPCと CIサーバそれぞれで 必要なタイミングで自動生成を実行 45
  46. 46. #ccc_a1 O/Rマッパーの自動生成コードもコミットしたい場合 ● マイグレーションSQL文のコミットと、 O/RマッパーのJavaコード自動生成の 実行&コミットを、同時にやるべき。 ● ということは、5頁前のような並行作業だとコンフリクトを起こし やすくなる。 ○ 特に自動生成したJavaコード部分のコンフリクト ● ということは、直列にしか作業できない(かもしれない) 46 割愛
  47. 47. #ccc_a1 DBFlute = 自動生成コードをコミットする前提 ● 例:他のカラムから導出、計算した結果を入れるプロパティを、 自動生成したエンティティクラスに追加 ● 例:共通のWHERE句を組み立て易くするために検索条件生 成クラスに自作のメソッドを追加 (正確には加筆用の継承クラスがあらかじめ自動生成される) 47
  48. 48. #ccc_a1 8. O/Rマッパーが自動生成したJavaコードと ドメインオブジェクトのコードを 分けるべきか? 注:DDDのそれというよりはDTOに近いかも 48
  49. 49. #ccc_a1 がぜん、分けるべき。 49 RDB Repository Logic O/Rマッパー 自動生成したentity クラス ドメインクラス /DTO ドメインクラス /DTO Controller ドメインクラス /DTO ● setter/getterで地 道に詰め替え ● MapStruct, Dozer, etc 長寿 長寿に なりがち コロコロ変 わる
  50. 50. #ccc_a1 ドメインクラスと自動生成クラスの名前衝突に注意 テーブル名 BOOK O/Rマッパが自動生成したエンティティクラスやメタデータクラス名 Book.java 丹念に手作りしたいDDD的なドメインクラスの名前 Book.java 50 名前衝突
  51. 51. #ccc_a1 // jOOQでのカスタム例 public class FooPrefixGeneratorStrategy extends DefaultGeneratorStrategy { @Override public String getJavaClassName(final Definition definition, final Mode mode) { String name = super.getJavaClassName(definition, mode); switch (mode) { case POJO: return name + "Vo"; // エンティティクラスは BookVo.javaになる case DEFAULT: return 'Foo' + name; // メタデータクラスは FooBook.javaになる } return name; } 51 (正確にはTablesクラスの内部クラス)
  52. 52. #ccc_a1 ちょっと休憩 52 1. 水を飲む 2. 時間を確認 35分くらい?
  53. 53. #ccc_a1 9. テストデータの投入方法は? 53
  54. 54. #ccc_a1 テストデータは必須。しかし.... 54 ● 空っぽのテーブルでアプリケーションの動作確認はできない ● テストデータは固定ではない。特に日付。 ○ 「発売前の本」のつもりのデータが常に 2018-12-15 だったら?
  55. 55. #ccc_a1 DBFluteの場合 55 ‘replace-schema’コマンドが 1. 全てのテーブル、インデックスを DROP -> CREATE 2. xls, tsv, csvファイルがあればテストデータとしてINSERT csvファイル上の “$sysdate.addDay(7)” は コマンド実行時刻の7日後の値がDBカラムに入る
  56. 56. #ccc_a1 他の方法 56 A. RDBMSのcsv, tsvのバルクロード機能 a. 日付の相対指定が難しい B. INSERT文を用意して実行 a. 大量の手書きINSERT文が今後のDB変更に耐えられるか? C. 上記A,Bのハイブリッド a. csvで入れて相対日付カラムはUPDATE文 D. FlywayのJava-Based Migration a. DB定義変更用PJとは別PJとしてテスデータ用PJを作っておく b. SQL文ではなくJavaコードを作っておく c. INSERT文よりは楽。日付の相対指定も可能。
  57. 57. #ccc_a1 Flyway公式マニュアルによると 57 出典 *10 src/main/java/db/migration/V1_2__Another_user.java src/main/resources/db/migration/V1_3__HogeHoge.sql ./gradlew flywayMigrate でファイル名順に実行される ループして値を変えながらINSERTすればいい
  58. 58. #ccc_a1 10. O/Rマッパが作るSQLを見たい 58 - 手書きのSQL以外は信用しないタイプのエンジニアのため に -
  59. 59. #ccc_a1 DBFluteの場合 ● デフォルトでこんな感じ ○ SqlLogHandlerでさらに細かい制御も可能 59 出典 *5 結果データも出てる 呼び出し元クラス
  60. 60. #ccc_a1 jOOQのデバッグログ 60 出典 *6
  61. 61. #ccc_a1 O/Rマッパーを問わない方法もある ● JDBCドライバの中継器として稼働しつつ 実行しようとしているSQLをログ出力 (正確にはプリペアドステートメントだけのことがほとんど) ○ p6spy ○ log4jdbc 61
  62. 62. #ccc_a1 11. RDBMSの独自関数を使いたい 62
  63. 63. #ccc_a1 DBFluteの場合 ● sql_calc_found_rowsくらいならデ フォルト対応 ● 外出しSQLならなんでも書ける ● フォーマットは2-Way-SQL ● 呼び出し側コード (WHERE句の調整等)も 自動生成 63 出典 *7
  64. 64. #ccc_a1 jOOQの場合 64 出典 *8,9
  65. 65. #ccc_a1 12. テーブル定義書をどう作るか 65
  66. 66. #ccc_a1 自動生成 一択 66
  67. 67. #ccc_a1 DBFluteの場合 67 ‘doc’コマンド一発
  68. 68. #ccc_a1 SchemaSpyの場合 ● jarを直接実行、あるいはdocker run (*11) ● ER図も自動生成 68
  69. 69. #ccc_a1 13. 同じプロジェクトで 複数のO/Rマッパーを同時に使う or乗り換えるためのヒント 69
  70. 70. #ccc_a1 ● 複数のO/Rマッパを好きに混ぜて使って、 いいとこどりできたら幸せ。 ● 一つのWeb/DBプロジェクトの開発で、 2つ以上のO/Rマッパーを混ぜて使うことは 無理?、危険? ● トランザクション管理ェェ... 70
  71. 71. #ccc_a1 ※ Spring Frameworkを使っているとして 71
  72. 72. #ccc_a1 72@Autowired OrderBhv orderBhv; // DBFlute @Autowired DSLContext dsl; // jOOQ @Transactional public void order(String isbn, Long memberId) { // 本を購入するメソッド Order order = new Order(); order.setIsbn(isbn); order.setMemberId(memberId); orderBhv.insert(order); Book book = Tables.Book; dsl.update(book) .set(book.STOCK, book.STOCK.minus(1)) .where(book.ISBN.eq(isbn)) .execute(); } ● DBFluteでINSERT ● jOOQでUPDATE ● 一つのトランザクション (BIGIN〜 COMMIT) で実行されていればOK DBFlute jOOQ
  73. 73. #ccc_a1 2つのO/Rマッパが使用する javax.sql.DataSource オブジェクトが 確実に同じ(インスタンス)であれば 正しくトランザクション管理できる。 73
  74. 74. #ccc_a1 @Bean public javax.sql.DataSource dataSource() { // コネクションプール機構を使うとして(ここではHikariCP) HikariConfig config = new HikariConfig(); config.setJdbcUrl(...); config.setUsername(...); config.setPassword(...); HikariDataSource ds = new HikariDataSource(config); // return ds; // ←こうじゃなくて↓こう return new TransactionAwareDataSourceProxy(ds); } 74 詳しくは TransactionAwareDataSourceProxy でググる。
  75. 75. #ccc_a1 まとめ 75
  76. 76. #ccc_a1 Java/DB開発の今どきの手法とツール ● O/Rマッピングライブラリ ○ ソースコード自動生成によるタイプセーフ方式 ○ 外部SQLファイル実行方式 ● 実行したSQLのロギング ● DBマイグレーションの自動化 ● テストデータ投入の自動化 ● テーブル定義書の自動作成 ● トランザクションに気をつけて複数のO/Rマッパーを同時に使用 76 選択肢と使い方をよく吟味して、レッツ快適開発!
  77. 77. #ccc_a1 Thank you ! 77
  78. 78. #ccc_a1 参考文献 1. Hibernateはどのようにして私のキャリアを破滅寸前にしたか https://www.kaitoy.xyz/2017/02/23/how-hibernate-ruined-my-career/ 上記の原文 https://medium.com/@ggajos/how-hibernate-almost-ruined-16f31ba7d381 2. 我々はいかにして技術選択を間違えたのか? https://blog.cybozu.io/entry/2016/12/28/101500 3. https://groups.google.com/forum/#!msg/querydsl/fNFXliG8P-k/7dy2aAotVQ0J 4. https://blog.jooq.org/2014/05/29/querydsl-vs-jooq-feature-completeness-vs-now-more-than-ever/ 5. http://dbflute.seasar.org/ja/manual/function/genbafit/implfit/debuglog/index.html 6. https://www.jooq.org/doc/3.11/manual/sql-execution/logging/ 7. http://dbflute.seasar.org/ja/manual/function/ormapper/outsidesql/howto.html 8. https://www.jooq.org/doc/3.11/manual/sql-execution/query-vs-resultquery/ 9. https://www.jooq.org/doc/3.11/manual/sql-building/plain-sql 10. https://flywaydb.org/documentation/migrations#java-based-migrations 11. https://hub.docker.com/r/schemaspy/schemaspy/ 12. 78

×