Mybatisの1stレベルキャッシュ

に公開

概要

Mybatisには2種類のキャッシュ機構が存在し、デフォルトではセッション生存期間中のデータを保持する1stレベルキャッシュのみが有効です。これとは別に、明示的に有効化することで利用可能となる2ndレベルキャッシュが存在します(Mapper XML ファイル - chache)。

1stレベルキャッシュ 2ndレベルキャッシュ
デフォルト 有効 無効
キャッシュ単位 Session Mapper
キャッシュクリア commit,rollback,close insert,update,delete

1stレベルキャッシュは同一のトランザクションにおいて、同じステートメントを同じパラメータで呼び出す場合に有効になります。

以下はこの記事の続きとなる記事です。
Mybatisの1stレベルキャッシュ2

以下では1stレベルキャッシュ機構がどのように動作するのかを確認していきます。

確認環境

  • Java 21
  • Spring Boot 3.4.1
  • MyBatis Spring Boot Starter 3.0.4
  • PostgreSQL 16.4

確認方法

同じSession内で同じクエリを複数回呼び出しログを確認します。2回目以降に同じクエリが発行されずにレスポンスが返ってくることでキャッシュが機能していることを確認します。

依存関係

確認のためのプログラムの依存関係は以下のようになります。

build.gradle.kts
dependencies {
    implementation("org.springframework.boot:spring-boot-starter:3.4.1")
    implementation("org.springframework.boot:spring-boot-starter-jdbc:3.4.1")
    implementation("org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.4")
    implementation("org.postgresql:postgresql:42.7.4")
}

データベース

以下のテーブルとデータを用います。

テーブル定義

テーブル定義
                     Table "public.example"
 Column |         Type          | Collation | Nullable | Default 
--------+-----------------------+-----------+----------+---------
 id     | character varying(4)  |           | not null | 
 memo   | character varying(50) |           |          | 

データ

exampleテーブル
  id  |   memo   
------+----------
 0001 | example1
 0002 | example2

1stレベルキャッシュ確認

1stレベルキャッシュ確認プログラム

Spring Boot アプリケーション起動時に実行されるプログラムを作成して1stレベルキャッシュを確認します。以下のような構成になります。

構成
com.example
  +- ExampleApplication.java
  +- ExampleApplicationRunner.java
  +- ExampleRepository.java
  +- ExampleMapper.java
  +- ExampleMapper.xml

ExampleApplication はアプリケーションのエントリポイントです。

ExampleApplication.java
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ExampleApplication {
    public static void main(String[] args) {
        SpringApplication.run(ExampleApplication.class, args);
    }
}

ExampleApplicationRunnerApplicationRunner を実装します。このクラスがアプリケーション起動時に呼び出されます。

ExampleApplicationRunner.java
package com.example;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class ExampleApplicationRunner implements ApplicationRunner {
    private ExampleRepository exampleRepository;

    public ExampleApplicationRunner(ExampleRepository exampleRepository) {
        this.exampleRepository = exampleRepository;
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        exampleRepository.get();
    }
}

ExampleRepository はデータソースへアクセスするためのクラスです。

ExampleRepository.java
package com.example;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Repository
public class ExampleRepository {
    private final Logger log = LoggerFactory.getLogger(ExampleRepository.class);

    private ExampleMapper exampleMapper;

    public ExampleRepository(ExampleMapper exampleMapper) {
        this.exampleMapper = exampleMapper;
    }

    @Transactional
    public void get() {
        log.info("1回目");
        exampleMapper.get("0001");
        log.info("2回目: 1回目と同じパラメータで同じステートメントを呼び出す");
        exampleMapper.get("0001");
        log.info("3回目: 1回目と異なるパラメータで同じステートメントを呼び出す");
        exampleMapper.get("0002");
        log.info("4回目: 3回目と同じパラメータで異なるステートメントを呼び出す");
        exampleMapper.get2("0002");
    }
}

ExampleMapper ではデータベース操作に対応するメソッドを宣言します。

ExampleMapper.java
package com.example;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface ExampleMapper {
    List<Map<String, Object>> get(@Param("id") String id);
    List<Map<String, Object>> get2(@Param("id") String id);
}

ExampleMapper.xml にクエリを記述します。

ExampleMapper.xml
<?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.ExampleMapper">
    <select id="get" resultType="hashmap">
        select * from example where id = #{id};
    </select>

    <select id="get2" resultType="hashmap">
        select * from example where id = #{id};
    </select>
</mapper>

1stレベルキャッシュ確認プログラム実行結果

結果
com.example.ExampleRepository            : 1回目
org.mybatis.spring.SqlSessionUtils       : Creating a new SqlSession
org.mybatis.spring.SqlSessionUtils       : Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5d97caa4]
o.m.s.t.SpringManagedTransaction         : JDBC Connection [HikariProxyConnection@1283906060 wrapping org.postgresql.jdbc.PgConnection@7d44a19] will be managed by Spring
com.example.ExampleMapper.get            : ==>  Preparing: select * from example where id = ?;
com.example.ExampleMapper.get            : ==> Parameters: 0001(String)
com.example.ExampleMapper.get            : <==      Total: 1
org.mybatis.spring.SqlSessionUtils       : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5d97caa4]
com.example.ExampleRepository            : 2回目: 1回目と同じパラメータで同じステートメントを呼び出す
org.mybatis.spring.SqlSessionUtils       : Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5d97caa4] from current transaction
org.mybatis.spring.SqlSessionUtils       : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5d97caa4]
com.example.ExampleRepository            : 3回目: 1回目と異なるパラメータで同じステートメントを呼び出す
org.mybatis.spring.SqlSessionUtils       : Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5d97caa4] from current transaction
com.example.ExampleMapper.get            : ==>  Preparing: select * from example where id = ?;
com.example.ExampleMapper.get            : ==> Parameters: 0002(String)
com.example.ExampleMapper.get            : <==      Total: 1
org.mybatis.spring.SqlSessionUtils       : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5d97caa4]
com.example.ExampleRepository            : 4回目: 3回目と同じパラメータで異なるステートメントを呼び出す
org.mybatis.spring.SqlSessionUtils       : Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5d97caa4] from current transaction
com.example.ExampleMapper.get2           : ==>  Preparing: select * from example where id = ?;
com.example.ExampleMapper.get2           : ==> Parameters: 0002(String)
com.example.ExampleMapper.get2           : <==      Total: 1
org.mybatis.spring.SqlSessionUtils       : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5d97caa4]

1回目では、SqlSessionUtils によって新しい SqlSession が作成されていることが分かります。また、キャッシュは空であるため以下のようなクエリ発行ログが出力されています。

com.example.ExampleMapper.get            : ==>  Preparing: select * from example where id = ?;
com.example.ExampleMapper.get            : ==> Parameters: 0001(String)
com.example.ExampleMapper.get            : <==      Total: 1

2回目では、同じトランザクション内なので同じSqlSessionが再利用されています。

com.example.ExampleRepository            : 2回目: 1回目と同じパラメータで同じステートメントを呼び出す
org.mybatis.spring.SqlSessionUtils       : Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5d97caa4] from current transaction

クエリに関するログが一切なく、再度発行されていないことが分かります。

3回目のクエリ発行時はキャッシュを使用していません。同じステートメントを呼び出していますが、パラメータが異なるためです。

com.example.ExampleRepository            : 3回目: 1回目と異なるパラメータで同じステートメントを呼び出す
org.mybatis.spring.SqlSessionUtils       : Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5d97caa4] from current transaction
com.example.ExampleMapper.get            : ==>  Preparing: select * from example where id = ?;
com.example.ExampleMapper.get            : ==> Parameters: 0002(String)
com.example.ExampleMapper.get            : <==      Total: 1

4回目のクエリ発行時も3回目と同様にキャッシュを使用していません。発行しているのは3回目と同じクエリですが呼び出すステートメントが異なります。こうした場合は、キャッシュが使用されないことが分かりました。

com.example.ExampleRepository            : 4回目: 3回目と同じパラメータで異なるステートメントを呼び出す
org.mybatis.spring.SqlSessionUtils       : Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5d97caa4] from current transaction
com.example.ExampleMapper.get2           : ==>  Preparing: select * from example where id = ?;
com.example.ExampleMapper.get2           : ==> Parameters: 0002(String)
com.example.ExampleMapper.get2           : <==      Total: 1

参考

  1. Mybatis.org (Mapper XML ファイル - chache)

Discussion