Deep Dive into MyBatis

検証環境

  • java 11.0.1 2018-10-16 LTS
  • MyBatis 3.4.6

サンプルコード

以下を実行しながら、処理の流れを追っていく。 複雑な箇所は適宜、シーケンス図を作成する。(メインの流れではないと判断したところは適宜省略して記載するので、正確さにはやや欠けます)

public static void main(String[] args) {
  HikariDataSource ds = new HikariDataSource();
  ds.setUsername("dev");
  ds.setDriverClassName("org.postgresql.Driver");
  ds.setJdbcUrl("jdbc:postgresql://192.168.11.116:5432/dev");
  ds.setPassword("secret");

  // (1)
  Environment env = new Environment("development",
      new JdbcTransactionFactory(), ds);
  // (2)
  Configuration conf = new Configuration(env);
  // (3)
  conf.addMapper(SampleRepository.class);
  // (4)
  SqlSessionFactory sqlSessionFactory =
      new SqlSessionFactoryBuilder().build(conf);
  // (5)
  try(SqlSession sqlSession = sqlSessionFactory.openSession()) {
  // (6)
    SampleRepository sampleRepository = sqlSession.getMapper(SampleRepository.class);
  // (7)
    sampleRepository.findAll()
        .stream()
        .forEach(System.out::println);
  }
}

今回はXMLを利用せずに、アノテーションでSQLを記述する。

public interface SampleRepository {
  @Select("SELECT * FROM sample")
  List<String> findAll();
}

データベースは以下を用意する。

dev=# \d sample
                      Table "public.sample"
 Column |          Type          | Collation | Nullable | Default
--------+------------------------+-----------+----------+---------
 name   | character varying(100) |           | not null |
Indexes:
    "sample_pkey" PRIMARY KEY, btree (name)
dev=# SELECT * FROM sample;
  name
--------
 alice
 bob
 daniel
(3 rows)

サンプルコードの実行結果は以下の通り。 例外が発生せず、データが正しく取得できていることがわかる。

alice
bob
daniel

クラス概要

クラス名 概要
Environment 環境に依存する情報を持つ(データソースやトランザクションマネージャー)
Configuration MyBatis実行に必要な設定(設定を参照)やMappedStatementを持つ
MappedStatement select/insert/delete/update といった、1つのクエリを表現する。parameterTypeやresultTypeなど、クエリ実行に必要な情報を持つ(設定可能な項目はMapper XML ファイルを参照)
SqlSessionFactory SqlSessionを生成するファクトリークラス
SqlSession コマンドの実行、Mapperの取得、トランザクションの管理をするための、クライアントとのインタフェース
Executor 実際にクエリを実行する役割のクラス。SIMPLE、BATCH、REUSEの3種類ある。
TransactionFactory Transactionを生成するファクトリークラス
Transaction トランザクションを抽象化したクラス。トランザクション管理をAPサーバが行うことがあるのでmanagedとjdbcの2種類ある
TypeHandler クエリの実行結果をJavaの型に変換するクラス

処理の流れを追おう

(1) new Environment

プロパティにデータ入れて初期化してるだけ。

(2) new Configuration

XML上で特別な意味を持つ文字列とJavaのクラスとの対応付けをしている。(例えば、datasourceタグの属性のJDBCとJdbcTransactionFactory.class、といった具合に) が、今回はSQL定義もMyBatisの設定もXMLを利用してないので省略する。

あとは、プロパティにデータ入れて初期化してるだけ。

(3) Configuration#addMapper()

主に、アノテーションやXMLを解析してMappedStatementを組み立てる。

MappedStatementは、クエリに関する情報を持つ。 作成した MappedStatement は、Configurationに保管する。

f:id:kimulla:20191202224910p:plain

以降は、MappedStatementを基にクエリに関する情報を取得し、処理する。

(4) SqlSessionFactoryBuilder#build()

Configurationを引数に DefaultSqlSessionFactory を生成するだけ。

MyBatisの設定をXMLで書いた場合は、XMLからConfigurationクラスへの変換処理を事前に行ったのちに、DefaultSqlSessionFactoryを生成する。

(5) SqlSessionFactory#openSession()

主に、SqlSessionを生成する。

トランザクションを抽象化したTransactionを、TransactionFactoryから生成する。 また、クエリの実行を行うExecutorのタイプ(SIMPLE、BATCH、REUSE)を選択し、生成する。 上記インスタンスとConfigurationをもとに、SqlSessionを生成する。

f:id:kimulla:20191202224922p:plain

(6) SqlSession#getMapper(Class\)

主にMapperインタフェースの実装クラスを生成する。

具体的には、Proxy#newInstance() で MapperProxy を挟み込んだインスタンスを生成する。MapperProxyはクエリ実行のために、SqlSessionを保持する。

f:id:kimulla:20191202224934p:plain

(7) SampleRepository#findAll()

主に、クエリを実行する。

Mapperメソッドを実行すると、MapperProxy#invokeが実行される。 MapperMethod は、Mapperメソッドの型情報やMappedStatementからSqlSessionのメソッドを決定する。(今回はselectList) Executorは、コネクションを取得してクエリを実行する。クエリ実行にはJDBC DriverのAPIが利用される。 ResultSetから戻り値のJavaの型に変換する部分はResultSetHandlerとTypeHandlerが実行する。TypeHandlerは型ごとに実装が用意されている。

f:id:kimulla:20191202224945p:plain

最後に

大体の流れは理解できたと思う。

あとは、各種キャッシュの実装があるようなので、そこらへんの理解を深めたいところ。