Mybatisの各SQL文の実行時間の統計

Mybatisの各SQL文の実行時間の統計

背景

最近、面接でデータベース トランザクションについてよく質問されます。通常は、@Transactional アノテーションを追加することしか知らないので、混乱してしまいます。この部分は本当に見落とされがちですが、面接官は、あまり使われないけれども非常に重要なこの種の質問を好み、不意を突かれるのです。もう無駄な話はやめて、本題に入りましょう。

ソリューション 1: アスペクト プログラミング @Aspect

このソリューションは、主にマッパー パッケージの下にあるインターフェース メソッドを囲むようにラップし、前後の時間差を計算します。これは典型的な AOP の知識です。この計算は大まかですが、これも解決策です。具体的な方法は以下の通りです。

@側面
@成分
翻訳者
パブリッククラス MapperAspect {

  @AfterReturning("実行(* cn.xbmchina.mybatissqltime.mapper.*Mapper.*(..))")
  パブリック void logServiceAccess(JoinPoint joinPoint) {
    log.info("完了: " + joinPoint);
  }


  /**
   * cn.xbmchina.mybatissqltime.mapper のすべてのパブリック メソッドを監視します。*Mapper パッケージとそのサブパッケージ*/
  @Pointcut("実行(* cn.xbmchina.mybatissqltime.mapper.*Mapper.*(..))")
  プライベートボイドポイントカットメソッド(){
  }

  /**
   * 通知に関する宣言 *
   * @param pjp
   * @戻る
   * @throws スロー可能
   */
  @Around("ポイントカットメソッド()")
  パブリックオブジェクトdoAround(ProceedingJoinPoint pjp)はThrowableをスローします{
    長いbegin = System.nanoTime();
    オブジェクト obj = pjp.proceed();
    長い終了 = System.nanoTime();

    log.info("Mapper メソッドの呼び出し: {}、パラメータ: {}、実行時間: {} ナノ秒、消費時間: {} ミリ秒",
        pjp.getSignature().toString()、配列.toString(pjp.getArgs())、
        (終了 - 開始)、(終了 - 開始) / 1000000);
    obj を返します。
  }
}

解決策2: mybatisプラグイン

MyBatis では、4 つの主要オブジェクトの作成プロセスにプラグインが介入します。プラグインは、動的プロキシ メカニズムを使用してターゲット オブジェクトをレイヤーごとにラップし、ターゲット メソッドを実行する前にターゲット オブジェクトをインターセプトする効果を実現できます。

MyBatis では、マップされたステートメントの実行中に特定の時点で呼び出しをインターセプトできます。

デフォルトでは、MyBatis はプラグインが以下のメソッド呼び出しをインターセプトすることを許可します。

①エグゼキュータ(update、query、flushStatements、commit、rollback、getTransaction、close、isClosed)
②ParameterHandler(getParameterObject, setParameters)
③ResultSetHandler(結果セットの処理、出力パラメータの処理)
④StatementHandler(準備、パラメータ化、バッチ、更新、クエリ)

コードは次のとおりです:

org.apache.ibatis.executor.statement.StatementHandler をインポートします。
org.apache.ibatis.mapping.BoundSql をインポートします。
org.apache.ibatis.mapping.ParameterMapping をインポートします。
org.apache.ibatis.plugin.Interceptor をインポートします。
org.apache.ibatis.plugin.Intercepts をインポートします。
org.apache.ibatis.plugin.Invocation をインポートします。
org.apache.ibatis.plugin.Plugin をインポートします。
org.apache.ibatis.plugin.Signature をインポートします。
org.apache.ibatis.session.ResultHandler をインポートします。
org.slf4j.Logger をインポートします。
org.slf4j.LoggerFactory をインポートします。
org.springframework.stereotype.Component をインポートします。

java.sql.Statement をインポートします。
java.util.List をインポートします。
java.util.Properties をインポートします。

/**
 *SQL 実行時間記録インターセプター*
 * @著者ゼロ
 * 2019年12月13日 17時05分28秒
 */
@Intercepts({@Signature(type = StatementHandler.class、method = "query"、args = {Statement.class、ResultHandler.class})、
    @Signature(type = StatementHandler.class、メソッド = "update"、引数 = {Statement.class})、
    @Signature(type = StatementHandler.class、メソッド = "batch"、引数 = {Statement.class})})
@成分
パブリッククラスSqlExecuteTimeCountInterceptorはInterceptorを実装します{

  プライベート静的 Logger ロガー = LoggerFactory.getLogger(SqlExecuteTimeCountInterceptor.class);

  /**
   * 印刷されるパラメータ文字列の最大長 */
  プライベート最終静的int MAX_PARAM_LENGTH = 50;

  /**
   * レコードの最大SQL長 */
  プライベート最終静的int MAX_SQL_LENGTH = 200;


  @オーバーライド
  パブリックオブジェクトインターセプト(呼び出し呼び出し)はThrowableをスローします{
    オブジェクトターゲット = invocation.getTarget();
    長い開始時間 = System.currentTimeMillis();
    ステートメント ハンドラ ステートメント ハンドラ = (ステートメント ハンドラ) ターゲット;
    試す {
      呼び出し.proceed() を返します。
    ついに
      長い endTime = System.currentTimeMillis();
      長いtimeCount = endTime - startTime;

      BoundSql は、 statementHandler.getBoundSql() によって実装されます。
      文字列 sql = boundSql.getSql();
      オブジェクトparameterObject = boundSql.getParameterObject();
      リスト<ParameterMapping> パラメータマッピングリスト = boundSql.getParameterMappings();

      // SQL ステートメントをフォーマットし、改行を削除し、パラメータを置き換えます。sql = formatSQL(sql, paramObject, paramMappingList);

      logger.info("SQL を実行: [, {}] 実行時間 [{} ミリ秒]", sql, timeCount);
    }
  }


  /**
   * SQL 文のフォーマット/整形*
   * @param sql SQL ステートメント * @param param parameterObject パラメータ マップ
   * @param パラメータマッピングリスト パラメータリスト
   * @return フォーマットされたSQL
   */
  プライベート文字列formatSQL(文字列sql、オブジェクトparameterObject、リスト<ParameterMapping>parameterMappingList) {
    // 入力SQL文字列が空かどうかの判定 if (sql == null || sql.length() == 0) {
      戻る "";
    }
    // SQL を美しくする
    sql = beautifySql(sql);
    // パラメータが渡されないシナリオでは、SQL を整形して返します if (parameterObject == null || parameterMappingList == null || parameterMappingList.size() == 0) {
      SQL を返します。
    }
    LimitSQLLength(sql) を返します。
  }


  /**
   * 長さ制限後のSQL文を返します*
   *
   * @param sql 元のSQL文*/
  プライベート文字列LimitSQLLength(文字列sql) {
    (sql == null || sql.length() == 0)の場合{
      戻る "";
    }
    (sql.length() > MAX_SQL_LENGTH)の場合{
      sql.substring(0, MAX_SQL_LENGTH) を返します。
    } それ以外 {
      SQL を返します。
    }
  }


  @オーバーライド
  パブリックオブジェクトプラグイン(オブジェクトターゲット) {
    Plugin.wrap(target, this) を返します。
  }

  @オーバーライド
  パブリック void setProperties(プロパティ プロパティ) {

  }




  /**
   * SQL 内の ? に対応する値を置き換え、最初の 50 文字のみを保持します*
   * @param sql SQL ステートメント* @param valueOf ? 対応する値*/
  プライベート文字列replaceValue(文字列sql、文字列valueOf) {
    // 50 文字を超える場合は、最初の 50 文字のみを取得します if (valueOf != null && valueOf.length() > MAX_PARAM_LENGTH) {
      値 = 値.substring(0, MAX_PARAM_LENGTH);
    }
    sql = sql.replaceFirst("\\?", 値);
    SQL を返します。
  }

  /**
   * SQLを美しくする
   *
   * @param sql SQL ステートメント */
  プライベート文字列beautifySql(文字列sql) {
    sql = sql.replaceAll("[\\s\n]+", " ");
    SQL を返します。
  }
 }

解決策3: ドルイドを直接使用する

これは私たちが最もよく使うタイプですが、面接では一度言えばよく、聞く必要はないと思います。

Springboot+druid の構成 application.yml ファイルは次のとおりです。

春:
  データソース:
    URL: jdbc:mysql://localhost:3306/testdb1?characterEncoding=utf-8&useUnicode=true&useSSL=false&serverTimezone=UTC
    ドライバークラス名: com.mysql.jdbc.Driver # mysql8.0 以前では com.mysql.jdbc.Driver が使用されていました
    ユーザー名: root
    パスワード: root
    プラットフォーム:mysql
    #この構成により、Druid 接続プールが構成に導入されます。Spring はタイプを判別し、状況に応じてドライバー クラスを一致させるよう最善を尽くします。
    タイプ: com.alibaba.druid.pool.DruidDataSource
    ドルイド:
      initial-size: 5 # 初期化サイズmin-idle: 5 # 最小max-active: 100 # 最大値max-wait: 60000 # 接続を取得するまでの待機のタイムアウトを設定しますtime-between-eviction-runs-millis: 60000 # 閉じる必要があるアイドル接続をチェックおよび検出する間隔をミリ秒単位で設定しますmin-evictable-idle-time-millis: 300000 # アイドル接続をクリアするための最小アイドル時間をミリ秒単位で指定しますvalidationQuery: select 'x'
      test-while-idle: true # 接続がアイドル状態のときに接続テストを実行するかどうかtest-on-borrow: false # 接続プールから接続を借用するときに接続をテストするかどうかtest-on-return: false # 接続が接続プールに返されるときに接続をテストするかどうかfilters: config,wall,stat # 監視統計インターセプトのフィルターを構成します。フィルターを削除すると、監視インターフェイス sql をカウントできなくなります。'wall' はファイアウォールに使用されますpoolPreparedStatements: true # PSCache をオンにし、各接続の PSCache のサイズを指定しますmaxPoolPreparedStatementPerConnectionSize: 20
      最大オープン準備ステートメント数: 20
      # connectProperties プロパティを通じて mergeSql 関数を有効にします。遅い SQL レコード connectionProperties: druid.stat.slowSqlMillis=200;druid.stat.logSlowSql=true;config.decrypt=false
       #複数の DruidDataSources からの監視データをマージする#use-global-data-source-stat: true
      # WebStatFilter の設定手順については、Druid Wiki の Configuration_Configure WebStatFilter を参照してください。
      ウェブ統計フィルター:
        enabled: true #StatFilter を有効にするかどうか。デフォルト値は true です。
        URLパターン: /*
        除外: /druid/*、*.js、*.gif、*.jpg、*.bmp、*.png、*.css、*.ico
        セッション統計の有効化: true
        セッション統計最大数: 10
      #StatViewServlet の設定については、Druid Wiki の configure_StatViewServletConfigure stat-view-servlet を参照してください。
        enabled: true #StatViewServlet を有効にするかどうか。デフォルト値は true です。
        URLパターン: /druid/*
        リセット有効: true
        ログインユーザー名: admin
        ログインパスワード: admin

要約する

以上がこの記事の全内容です。皆様の勉強のお役に立てれば幸いです。また、123WORDPRESS.COM を応援していただければ幸いです。

以下もご興味があるかもしれません:
  • Mybatis プラグイン: SQL の印刷とその実行時の実装方法
  • Java は mybatis インターセプターを使用して SQL 実行時間をカウントする例

<<:  nginx で正規表現を使用してワイルドカードドメイン名を自動的に一致させる方法

>>:  JS は VUE コンポーネントに基づいて都市リスト効果を実装します

推薦する

Linux仮想マシンを作成し、仮想マシンネットワークを設定する方法に関するVMwareの詳細なチュートリアル

VMware で Linux 仮想マシンを作成し、VMware と仮想マシンのネットワークを設定する...

マインスイーパゲームを実装するための jQuery プラグイン (2)

この記事では、jQueryプラグインを使用してマインスイーパゲームを実装する2番目の記事を参考までに...

WeChat アプレット開発フォーム検証 WxValidate の使用

個人的には、WeChat アプレットの開発フレームワークは VUE と概ね似ていると感じていますが、...

Linuxカーネルの浮動小数点演算のサポートに関する簡単な説明

現在、ほとんどの CPU は浮動小数点ユニット (FPU) をサポートしています。FPU は、プロセ...

CentOS7環境にMySQL5.5データベースをインストールする

目次1. 現在のシステムにMySQLがインストールされているかどうかを確認する2. インストールされ...

MySQL5.6.31 winx64.zip インストールと設定のチュートリアル

#1. ダウンロード # #2. ローカルに解凍し、必要な構成のmy*.iniを変更します。 #3....

WeChatミニプログラムビデオ集中砲火位置ランダム

この記事では、WeChatミニプログラムのビデオ弾幕の位置をランダム化するための具体的なコードを紹介...

HTML内のフレームセットタグが正常に表示されない原因の解析と解決方法

<frameset></frameset>は皆さんもよくご存知のものです。こ...

Linux (Centos7) での redis5 クラスターの構築と使用方法の詳細な説明

目次1. 簡単な説明2. クラスターを作成する手順2.1. ディレクトリを作成する2.2. ソースコ...

JS オブジェクトのコピー (ディープ コピーとシャロー コピー)

目次1. 浅いコピー1. Object.assign(ターゲット、ソース、ソース...) 2. スプ...

史上最もクリエイティブな404ページのデザインは、ウェブサイトのユーザーエクスペリエンスを効果的に向上させます

ウェブを閲覧しているときに 404 ページに遭遇することはあまりないので、見落としがちです。しかし、...

インターネットウェブデザインにおけるバイオニックデザインの簡単な紹介

バイオニックデザインといえば、飛行機の発明、ドバイのブルジュ・アル・アラブ、平泳ぎなどを思い浮かべる...

Linux/Mac MySQL パスワードを忘れた場合の対処方法

Linux/Mac の MySQL パスワードを忘れた場合はどうすればいいですか?心配しないでくださ...

数ステップでサイバーパンク2077風の視覚効果を実現するCSS

背景記事を始める前に、賽博朋克とは何か、賽博朋克2077とは何かを簡単に理解しましょう。サイバーパン...

Dockerコンテナにvimコマンドがない問題を解決する方法

問題を見つける今日、Docker コンテナ内のファイルを変更しようとしたところ、コンテナ内に vim...