$30 off During Our Annual Pro Sale. View Details »

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

Yu Watanabe
December 18, 2018

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

Yu Watanabe

December 18, 2018
Tweet

More Decks by Yu Watanabe

Other Decks in Technology

Transcript

  1. #ccc_a1 Software Design 2019 / 1月号に寄稿しました 第2特集 リリースモデルの変更にどう対処する? Javaのバージョン問題に前向きに 取り組む方法

    第3章 Javaをバージョンアップしやすくする アイデア 進化に臆さず,そのメリットを 享受するために 4
  2. #ccc_a1 さっそくですがアンケート • MyBatis(iBatis) • SpringのJdbcTemplate • Hibernate • QueryDSL

    • jOOQ • Doma • DBFlute • S2JDBC • Flyway 過去1年であなたが実際に仕事で使ったものは? 6
  3. #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
  4. #ccc_a1 SpringのJdbcTemplate List<Book> books = jdbcTemplate.query( “SELECT ISBN, TITLE FROM

    BOOKS” + “ WHERE ISBN = ? ”, new Object[]{“hoge”}, // “?”のところに入れたい引数 new BeanPropertyRowMapper(Book.class) ); 16
  5. #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
  6. #ccc_a1 いま紹介したのは旧来型O/Rマッパー • メリット ◦ とにかくSQLを手で書かないと気が済まない人 • デメリット ◦ タイプセーフではない

    ◦ BOOKをBOOKSと書いても実行するまで(バグるまで)ミ スに気づけない ※想定しているテーブル名はBOOKです。前のページは わざと間違いを書いています。 18
  7. #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
  8. #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 割愛
  9. #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のマイグレーション機能
  10. #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
  11. #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に記録されていれば、何も起きな い(べき等性)
  12. #ccc_a1 補足 • 運用中のDBに、途中から導入することも可能 ◦ Flyway ▪ “flyway baseline” でググる

    ◦ DBFlute ▪ 詳しくはマニュアルを ▪ O/Rマッパーとして使わずとも、他のマイグレーション 支援コマンド群だけ使うことが可能 34
  13. #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を同時に書き続ける
  14. #ccc_a1 ローカルDB方式 = Docker時代のデファクト 41 $ docker run mysql:5.7 $

    ./gradlew flywayMigrate • 不要なカラムを削除したい • 不適切な名前のカラムを RENAMEしたい • 新しい機能のために新しい テーブルを追加したい • 並行して作業できる • ただしFlywayの場合はsqlファイルのバージョン番号 だけは衝突しないように話し合う $ docker run mysql:5.7 $ ./gradlew flywayMigrate
  15. #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
  16. #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
  17. #ccc_a1 がぜん、分けるべき。 49 RDB Repository Logic O/Rマッパー 自動生成したentity クラス ドメインクラス

    /DTO ドメインクラス /DTO Controller ドメインクラス /DTO • setter/getterで地 道に詰め替え • MapStruct, Dozer, etc 長寿 長寿に なりがち コロコロ変 わる
  18. #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クラスの内部クラス)
  19. #ccc_a1 DBFluteの場合 55 ‘replace-schema’コマンドが 1. 全てのテーブル、インデックスを DROP -> CREATE 2.

    xls, tsv, csvファイルがあればテストデータとしてINSERT csvファイル上の “$sysdate.addDay(7)” は コマンド実行時刻の7日後の値がDBカラムに入る
  20. #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文よりは楽。日付の相対指定も可能。
  21. #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
  22. #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 でググる。
  23. #ccc_a1 Java/DB開発の今どきの手法とツール • O/Rマッピングライブラリ ◦ ソースコード自動生成によるタイプセーフ方式 ◦ 外部SQLファイル実行方式 • 実行したSQLのロギング

    • DBマイグレーションの自動化 • テストデータ投入の自動化 • テーブル定義書の自動作成 • トランザクションに気をつけて複数のO/Rマッパーを同時に使用 76 選択肢と使い方をよく吟味して、レッツ快適開発!
  24. #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