Anarchy In the 1K

MyBatis Dynamic SQLを使ってみた。

背景

 Java + MyBatisを使って開発を行っているのですが、コード上でSQLの組み立てを行う方法が無いか調べたところ、MyBatis Dynamic SQLを使うと実現できる様です。

 以下のQuick Startを元に試してみたので、ここではその際のメモを残そうと思います。
MyBatis Dynamic SQL – MyBatis Dynamic SQL Quick Start

ハンズオン

用意するクラス, インターフェース

  • Supportクラス(テーブル, カラムの定義を行う。)
  • Mapperインタフェース
  • Mapperを呼び出すクラス
  • Entityクラス

実装例

コード全体は以下より取得可能です。
GitHub - U0326/code-example-mybatis_dynamic_sql

事前準備

CREATE TABLE friends (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(10)
);

上記の通り作成したテーブルを利用します。

Supportクラス

public class FriendDynamicSqlSupport {
  public static final Friend friend = new Friend(); // 1
  public static final SqlColumn<Integer> id = friend.id; // 1
  public static final SqlColumn<String> mame = friend.name; // 1

  public static final class Friend extends SqlTable { // 2
    public final SqlColumn<Integer> id = column("id", JDBCType.INTEGER);
    public final SqlColumn<String> name = column("name", JDBCType.VARCHAR);

    public Friend() {
      super("friends"); //3
    }
  }
}
  1. 2で作成する内部クラスとその変数をstatic参照可能にする為に、public staticな変数を定義する。
  2. SqlTableを継承した内部クラスを作成し、カラム定義を行う。
  3. [スキーマ名.]テーブル名を渡してSqlTableのコンストラクタを呼び出す。

Mapperインターフェース

上記で作成したSupportクラスをstatic importし、以下の通りCRUD操作を定義します。

Create

@Mapper
public interface FriendMapper {
  // Create
  @InsertProvider(type = SqlProviderAdapter.class, method = "insert") // 1
  int insert(InsertStatementProvider<FriendEntity> insertStatement); // 2
  default int insert(FriendEntity entity) { // 3
    return MyBatis3Utils.insert(this::insert, entity, friend, c -> c.map(mame).toProperty("name")); // 4
  }
  ...
}
  1. Createを行うメソッドであることを、アノテーションを用いて宣言する。
  2. InsertStatementProvider<Entityクラス>を引数に取るメソッドを定義する。
  3. MyBatis3Utilsを用いたdefaultメソッドを定義し、呼び元でEntityクラスをのみを引数にしたCreateを可能にする。
  4. 以下の通り引数を渡し、MyBatis3Utils.insertを呼び出す。
    • 第3引数: Supportクラス内のSqlTableを継承したクラスのstatic参照
    • 第4引数: Supportクラス内のSqlColumnのstatic参照と、Entityクラスの変数名を以下の通り紐付けるラムダ式
      c -> c.map(SqlColumnのstatic参照).toProperty("Entityクラスの変数名")

Read

@Mapper
public interface FriendMapper {
  ...
  // Read
  @SelectProvider(type = SqlProviderAdapter.class, method = "select") // 1
  @Results( // 2
      {
        @Result(column = "id", jdbcType = JdbcType.INTEGER, property = "id", id = true),
        @Result(column = "name", jdbcType = JdbcType.VARCHAR, property = "name"),
      })
  Optional<FriendEntity> selectOne(SelectStatementProvider selectStatement); // 3
  default Optional<FriendEntity> selectOne(SelectDSLCompleter completer) { // 4
    return MyBatis3Utils.selectOne(this::selectOne, selectList, friend, completer); // 5
  }
  BasicColumn[] selectList = BasicColumn.columnList(id, name); // 6
  ...
}
  1. Readを行うメソッドであることを、アノテーションを用いて宣言する。
  2. カラム名とEntityクラスの変数名を以下の通り紐付ける。
    @Result(column = "カラム名", jdbcType = カラムの型, property = "Entityクラスの変数名")
  3. SelectStatementProviderを引数に取るメソッドを定義する。
  4. MyBatis3Utilsを用いたdefaultメソッドを定義し、呼び元でSELECT対象のカラムを指定せずReadを可能にする。
  5. 以下の通り引数を渡し、MyBatis3Utils.selectOneを呼び出す。
    • 第2引数: 6で定義するSELECT対象のカラムの配列
    • 第3引数: Supportクラス内のSqlTableを継承したクラスのstatic参照
  6. Supportクラス内のSqlColumnのstatic参照を用いて、SELECT対象のカラムの配列を定義する。

Update

@Mapper
public interface FriendMapper {
  ...
  // Update
  @UpdateProvider(type = SqlProviderAdapter.class, method = "update") // 1
  int update(UpdateStatementProvider updateStatement); // 2
  default int update(UpdateDSLCompleter completer) { // 3
    return MyBatis3Utils.update(this::update, friend, completer); // 4
  }
  ...
}
  1. Updateを行うメソッドであることを、アノテーションを用いて宣言する。
  2. UpdateStatementProviderを引数に取るメソッドを定義する。
  3. MyBatis3Utilsを用いたdefaultメソッドを定義し、呼び元でテーブル名を指定せずにUpdateを可能にする。
  4. 以下の通り引数を渡し、MyBatis3Utils.updateを呼び出す。
    • 第2引数: Supportクラス内のSqlTableを継承したクラスのstatic参照

Delete

@Mapper
public interface FriendMapper {
  ...
  // Delete
  @DeleteProvider(type = SqlProviderAdapter.class, method = "delete") // 1
  int delete(DeleteStatementProvider deleteStatement); // 2
  default int delete(DeleteDSLCompleter completer) { // 3
    return MyBatis3Utils.deleteFrom(this::delete, friend, completer); // 4
  }
  ...
}
  1. Deleteを行うメソッドであることを、アノテーションを用いて宣言する。
  2. DeleteStatementProviderを引数に取るメソッドを定義する。
  3. MyBatis3Utilsを用いたdefaultメソッドを定義し、呼び元でテーブル名を指定せずにDeleteを可能にする。
  4. 以下の通り引数を渡し、MyBatis3Utils.deleteFromを呼び出す。
    • 第2引数: Supportクラス内のSqlTableを継承したクラスのstatic参照

Mapperを呼び出すクラス

Supportクラスをstatic importし、以下の通りCRUD操作を呼び出します。

public class FriendMapperCaller {
  @Autowired
  private FriendMapper friendMapper;

  public void execute() {
    // Create
    FriendEntity newFriend = FriendEntity.builder().name("Jon").build();
    friendMapper.insert(newFriend);

    // Read
    friendMapper.selectOne(c -> c.where(id, isEqualTo(1)))

    // Update
    friendMapper.update(c -> c.set(name).equalTo("Bob").where(id, isEqualTo(1)));

    // Delete
    friendMapper.delete(c -> c.where(id, isEqualTo(1)));
  }
}

参考資料