Upgrade to Pro — share decks privately, control downloads, hide ads and more …

【エキサイトブログリビルド】Spring Boot × MyBatis × FreeMarker を使って、データベースの接続先を安全に変更します。

【エキサイトブログリビルド】Spring Boot × MyBatis × FreeMarker を使って、データベースの接続先を安全に変更します。

2023/6/4 jjugの登壇資料

Naka Sho

June 12, 2023
Tweet

More Decks by Naka Sho

Other Decks in Technology

Transcript

  1. 自己紹介 2011/04 中小SIer 2015/01 上場Web系 2020/06 ベンチャーWeb系 2020/10 エキサイト株式会社 入社

    ...今に至る エキサイト株式会社 メディアプラットフォーム事業部 システムグループ  中尾正剛 趣味:ロードバイク、ボルダリング
  2. はじめに エキサイトブログを2023/3/31 にシステム全体 のリビルドが完了しました。 Spring Boot×MyBatis×FreeMarkerを使っ て、DBをSQL Server -> PostgreSQL

    に変更し ました。 切り替えを安全に行うために工夫した点があり ますので、その説明をさせていただきます。
  3. Azure -> AWS に SQL Server -> PostgreSQL に 社内で主に使われているクラウ

    ドサービスはAWSであるため コスト削減のため リビルドの目的 01 02
  4. 事前投入 一日目 データ移行 二日目 三日目 削除履歴テーブルを別途用意し、移行後は削除用テー ブルに入っているPKを基準に削除します。 トリガーを利用しました。 事前投入 一日目

    二日目 三日目 移行前 移行後 削 除 削 除 別テーブルに削除履歴を保存します 削 除 削 除 削除テーブルに入るとトリガーが発火し、削除されます
  5. テーブル名 移行時間 補足 AAA 0:00:06 BBB 0:00:06 CCC 0:00:06 DDD

    0:00:13 差分更新 EEE 0:01:00 差分更新 FFF 0:00:08 GGG 0:00:12 HHH 0:00:05 差分更新 データ移行
  6. SQLServer PostgreSQL 取得件数指定 top句 limit句 isolation Level with(NOLOCK) なし 文字列変更

    convert to_char ランダム値の生成 newId() random() ID 値の取得 SCOPE_IDENTIT Y() RETURNING ID 照合順序 Japanese_CI_AS ja_JP.UTF-8 null判定 isnull coalesce SQL ServerとPostgreSQL SQL構文の違い
  7. MyBatis x FreeMarker MyBatisとは - Javaでデータベースを扱うためのフレームワークです。 - 直接 JDBC を扱うコードを書きません。

    - クエリ引数やクエリ結果を手動で設定する必要がほとんどありません。 FreeMarkerとは - Javaのテンプレートエンジンです。 組み合わせると、テンプレートエンジンにSQLを書くことができます。 MyBatis x FreeMarkerを使うのは、SQL以外の決まった記述をなくして見 やすいコードにするためです。
  8. デフォルト(xml) FreeMarker <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD

    Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.demo.UserDataMapper"> <select id="findById" resultType="com.example.demo.UserData"> SELECT id , name FROM user WHERE id = #{id} and now() <![CDATA[ < ]]> birth_month </select> </mapper> SELECT id , name FROM user WHERE id = <@p name="id" /> and now() < birth_month MyBatis x FreeMarker FreeMarkerを使うとxmlの決まった記述がなくなり、見通 しが良くなりました。
  9. テキストブロック以前 テキストブロック @Mapper public interface UserCustomMapper { @Lang(FreeMarkerLanguageDriver.class) @Select("findById.ftl") List<Books>

    findById(@Param("id") Long id); } @Mapper public interface UserCustomMapper { @Lang(FreeMarkerLanguageDriver.class) @Select(""" SELECT id , name FROM user WHERE id = <@p name="id" /> AND now() < birth_month """) List<Books> findById(@Param("id") Long id); } MyBatis x FreeMarker テキストブロックを使うと、さらに見通しをよくできるよ うになりました。
  10. MyBatis x FreeMarker databaseIdProviderの登録 /** * JDBCドライバーを読み込んだ時、データベースの種別でdatabaseIdProviderを設定する * * @return

    databaseIdProvider */ @Bean(name = "databaseIdProvider") public VendorDatabaseIdProvider vendorDatabaseIdProvider() { VendorDatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider(); Properties vendorProperties = new Properties(); vendorProperties.put("PostgreSQL", "PostgreSQL"); vendorProperties.put("SQL Server", "sqlserver"); databaseIdProvider.setProperties(vendorProperties); return databaseIdProvider; }
  11. MyBatis x FreeMarker @Lang(FreeMarkerLanguageDriver.class) @Select(""" SELECT top 1 id ,

    name FROM user WHERE id = <@p name="id" /> AND now() < birth_month """, databaseId = "sqlserver") @Select(value = """ SELECT id , name FROM user WHERE id = <@p name="id" /> AND now() < birth_month limit 1 """, databaseId = "PostgreSQL") List<Books> findById(@Param("id") Long id); Top句とLimit句の場合
  12. MyBatis x FreeMarker @Lang(FreeMarkerLanguageDriver.class) @Select(""" SELECT id , name FROM

    user with(nolock) WHERE id = <@p name="id" /> AND now() < birth_month """, databaseId = "sqlserver") @Select(value = """ SELECT id , name FROM user WHERE id = <@p name="id" /> AND now() < birth_month """, databaseId = "PostgreSQL") List<Books> findById(@Param("id") Long id); wit h(nolock)の場合
  13. MyBatis x FreeMarker @Lang(FreeMarkerLanguageDriver.class) @Select(""" SELECT id , name FROM

    user <#if '${_databaseId}' == 'sqlserver'> WITH (NOLOCK) </#if> WHERE id = <@p name="id" /> AND now() < birth_month """) List<Books> findById(@Param("id") Long id); wit h(nolock)の場合
  14. MyBatis x FreeMarker エラーになります freemarker.core.InvalidReferenceException: The following has evaluated to

    null or missing: ==> _databaseId [in nameless template at line 4, column 21] _databaseIdが使えないエラーが発生しました。
  15. MyBatis x FreeMarker /** * FreemarkerLanguageDriverを独自にカスタマイズする設定 * * カスタマイズしているのは以下の通り *

    - ${_databaseId}にdatabaseIdの文字列を設定 * - http://mybatis.org/freemarker-scripting/jacoco/org.mybatis.scripting.freemarker/FreeMarkerSqlSource.java.html */ public static class CustomSqlSource extends FreeMarkerSqlSource { private String dbms; public CustomSqlSource(Template template, org.apache.ibatis.session.Configuration configuration, Version version) { super(template, configuration, version); this.dbms = configuration.getDatabaseId(); } @Override protected Object preProcessDataContext(Object dataContext, boolean isMap) { dataContext = super.preProcessDataContext(dataContext, isMap); if (isMap) { ((Map<String, Object>) dataContext).put("_databaseId", new SimpleScalar(this.dbms)); return dataContext; } ((ParamObjectAdapter) dataContext).putAdditionalParam("_databaseId", new SimpleScalar(this.dbms)); return dataContext; } }
  16. MyBatis x FreeMarker /** * FreemarkerLanguageDriverを独自にカスタマイズした設定を呼び出す */ public static class

    CustomFreemarkerLanguageDriver extends FreeMarkerLanguageDriver { @Override protected SqlSource createSqlSource(Template template, org.apache.ibatis.session.Configuration configuration) { return new CustomSqlSource( template, configuration, freemarkerCfg.getIncompatibleImprovements() ); } }
  17. MyBatis x FreeMarker @Lang(CustomFreemarkerLanguageDriver.class) @Select(""" SELECT id , name FROM

    user <#if '${_databaseId}' == 'sqlserver'> WITH (NOLOCK) </#if> WHERE id = <@p name="id" /> AND now() < birth_month """) List<Books> findById(@Param("id") Long id); wit h(nolock)の場合
  18. SQLServer PostgreSQL FreeMarker 取得件数指定 top句 limit句 アノテーションで分離 isolation Level with(NOLOCK)

    なし SQL内で分岐 文字列変更 convert to_char SQL内で分岐 ランダム値の生成 newId() random() SQL内で分岐 ID 値の取得 SCOPE_IDENTITY() RETURNING ID SQL内で分岐 照合順序 Japanese_CI_AS ja_JP.UTF-8 SQL内で分岐 null判定 isnull coalesce SQL内で分岐 MyBatis x FreeMarker SQL構文の違い