🐧

MyBatisでJavaのListをPostgreSQLのARRAYにマッピングする

2023/07/16に公開

MyBatisにはJavaの配列とDBのARRAYをマッピングするorg.apache.ibatis.type.ArrayTypeHandlerがありますが、JavaのListとマッピングすることはできません。

コードを書く上で、配列(例:String[])よりList(例:List<String>)を使いたいことも多いと思いますので、Listをマッピングする方法を書いていきます。

確認した際の各バージョンは下記の通りです。

  • PostgreSQL 15.3
  • Java 17
  • Spring Boot 3.1.1
  • MyBatis 3.5.13

テーブル定義

今回は下記のようなテーブルを定義します。

CREATE TABLE list_records (
  id serial,
  booleans boolean[],
  shorts smallint[],
  integers integer[],
  longs bigint[],
  floats real[],
  doubles double precision[],
  strings text[]
);

エンティティ

テーブルにあわせて、下記のようなエンティティクラスを定義します。

package com.github.onozaty.mybatis.pg.example.domain;

import java.util.List;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ListRecord {

    private int id;

    private List<Boolean> booleans;

    private List<Short> shorts;

    private List<Integer> integers;

    private List<Long> longs;

    private List<Float> floats;

    private List<Double> doubles;

    private List<String> strings;
}

リポジトリ

リポジトリです。
Listというかジェネリクス型だと型パラメータ毎に共通的にマッピングを指定することができないため、個別にTypeHandlerを指定する必要があります。

package com.github.onozaty.mybatis.pg.example.repository;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import com.github.onozaty.mybatis.pg.example.domain.ListRecord;
import com.github.onozaty.mybatis.pg.type.list.BooleanListTypeHandler;
import com.github.onozaty.mybatis.pg.type.list.DoubleListTypeHandler;
import com.github.onozaty.mybatis.pg.type.list.FloatListTypeHandler;
import com.github.onozaty.mybatis.pg.type.list.IntegerListTypeHandler;
import com.github.onozaty.mybatis.pg.type.list.LongListTypeHandler;
import com.github.onozaty.mybatis.pg.type.list.ShortListTypeHandler;
import com.github.onozaty.mybatis.pg.type.list.StringListTypeHandler;

@Mapper
public interface ListRepository {

    @Select("""
INSERT INTO list_records (
  booleans,
  shorts,
  integers,
  longs,
  floats,
  doubles,
  strings
)
VALUES (
  #{booleans, typeHandler=booleanListTypeHandler},
  #{shorts, typeHandler=shortListTypeHandler},
  #{integers, typeHandler=integerListTypeHandler},
  #{longs, typeHandler=longListTypeHandler},
  #{floats, typeHandler=floatListTypeHandler},
  #{doubles, typeHandler=doubleListTypeHandler},
  #{strings, typeHandler=stringListTypeHandler}
)
RETURNING *
    """)
    @Results({
            @Result(property = "booleans", column = "booleans", typeHandler = BooleanListTypeHandler.class),
            @Result(property = "shorts", column = "shorts", typeHandler = ShortListTypeHandler.class),
            @Result(property = "integers", column = "integers", typeHandler = IntegerListTypeHandler.class),
            @Result(property = "longs", column = "longs", typeHandler = LongListTypeHandler.class),
            @Result(property = "floats", column = "floats", typeHandler = FloatListTypeHandler.class),
            @Result(property = "doubles", column = "doubles", typeHandler = DoubleListTypeHandler.class),
            @Result(property = "strings", column = "strings", typeHandler = StringListTypeHandler.class)
    })
    ListRecord insert(ListRecord record);

    @Select("""
SELECT * FROM list_records
    """)
    @Results({
            @Result(property = "booleans", column = "booleans", typeHandler = BooleanListTypeHandler.class),
            @Result(property = "shorts", column = "shorts", typeHandler = ShortListTypeHandler.class),
            @Result(property = "integers", column = "integers", typeHandler = IntegerListTypeHandler.class),
            @Result(property = "longs", column = "longs", typeHandler = LongListTypeHandler.class),
            @Result(property = "floats", column = "floats", typeHandler = FloatListTypeHandler.class),
            @Result(property = "doubles", column = "doubles", typeHandler = DoubleListTypeHandler.class),
            @Result(property = "strings", column = "strings", typeHandler = StringListTypeHandler.class)
    })
    List<ListRecord> selectAll();

    @Select("""
SELECT * FROM list_records WHERE id = #{id}
    """)
    @Results({
            @Result(property = "booleans", column = "booleans", typeHandler = BooleanListTypeHandler.class),
            @Result(property = "shorts", column = "shorts", typeHandler = ShortListTypeHandler.class),
            @Result(property = "integers", column = "integers", typeHandler = IntegerListTypeHandler.class),
            @Result(property = "longs", column = "longs", typeHandler = LongListTypeHandler.class),
            @Result(property = "floats", column = "floats", typeHandler = FloatListTypeHandler.class),
            @Result(property = "doubles", column = "doubles", typeHandler = DoubleListTypeHandler.class),
            @Result(property = "strings", column = "strings", typeHandler = StringListTypeHandler.class)
    })
    ListRecord select(int id);

    @Update("""
UPDATE list_records
SET
  id = #{id},
  booleans = #{booleans, typeHandler=booleanListTypeHandler},
  shorts = #{shorts, typeHandler=shortListTypeHandler},
  integers = #{integers, typeHandler=integerListTypeHandler},
  longs = #{longs, typeHandler=longListTypeHandler},
  floats = #{floats, typeHandler=floatListTypeHandler},
  doubles = #{doubles, typeHandler=doubleListTypeHandler},
  strings = #{strings, typeHandler=stringListTypeHandler}
WHERE
  id = #{id}
    """)
    void update(ListRecord record);
}

TypeHandlerの設定

最初に書いた通り、MyBatisのArrayTypeHandlerではマッピングできないため、下記のライブラリで用意されたTypeHandlerを利用します。

依存関係を追加します。(Gradleを利用しているのでbuild.gradleに追加)

    implementation 'com.github.onozaty:mybatis-postgresql-typehandlers:1.0.2'

リポジトリでTypeHandlerのマッピングを書く際に、パッケージ名を含めた完全修飾クラス名で書くのは面倒なため、エイリアスを指定します。
Spring Bootのapplication.propertiesで、mybatis.type-aliases-packageにてパッケージ名を指定すると、そのパッケージ内のTypeHandlerがクラス名の先頭を小文字にした形でエイリアスが設定されます。

mybatis.type-aliases-package=com.github.onozaty.mybatis.pg.type.list

コード全体

コード全体は下記プロジェクトになります。
List以外にも、JavaのenumとPostgreSQLのenum、JavaのオブジェクトとPostgreSQLのJSONBをマッピングするためのコードが含まれています。

参考

Discussion