MyBatisでJavaのListをPostgreSQLのARRAYにマッピングする
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