やりたいこと
Spring + Mybatis(Mapper XMLでSQL文を定義)なときに、ObjectをPostgreSQLにJSONB型として格納したい
やり方
今回は2つ取り上げます。
まず思いつく方法
Java側で一旦Stringにして、格納するSQL文の中でJSONBにキャストします。Object→Stringにはcom.fasterxml.jackson.databind.ObjectMapper
を利用します。
Object data = hoge(); //格納したいデータ
ObjectMapper mapper = new ObjectMapper();
String objectJSON = mapper.writeValueAsString(data)
sqlClient.insert(objectJSON)
<insert id="insert">
insert into hogetable (data) values (#{objectJSON}::jsonb);
</insert>
ちゃんとStringがJSON形式になっていれば、格納されます。
ちょっと気になること
これで動くといえば動くのですが、できればJava内ではオブジェクトのまま持っておきたいところです。わざわざ呼び出す側が、呼び出される側が使いやすいように整形するのは煩雑です。
そこで、あくまでJava側ではオブジェクトのまま保持しておき、Mapper XMLの中でJSON形式に変換します。 そうすれば先程述べたような問題点は解決されます。
ObjectMapperでの処理をMyBatis側にさせる方法
まず、Mapperのインターフェースを以下のように設計します。ポイントは、Mapper XMLの中でcom.fasterxml.jackson.databind.ObjectMapper.writeValueAsString
を実行させるために、ObjectMapper
も引き受けるようにしているところです。
insert(@Param("objectMapper") ObjectMapper objectMapper, @Param("object") Object object);
呼び出す側はObjectMapper
と格納したいObjectを併せて渡すだけです。
Object data = hoge(); //格納したいデータ
ObjectMapper mapper = new ObjectMapper();
sqlClient.insert(mapper, data)
また、Mapper XMLではbind式を利用します。
<insert id="insert">
<bind name="objectJSON" value="objectMapper.writeValueAsString(object)" />
insert into hogetable (data) values ('${objectJSON}'::jsonb)
</insert>
bind式では、valueに書いた値がnameで指定した変数に代入されます。このとき、valueの中には値そのものだけではなく、式を書くこともできます。
その後のsql文では${変数名}
で参照できます。ただし、${変数名}
では、単なる置換にすぎないので、シングルクォーテーションで囲んで文字列(PostgreSQLではtext型)にしておく必要があります。また、本来は${}
を使うべきではありません。単なる置換のせいでSQLインジェクションに弱くなるからです。
本当はそのあたりを解決してくれる#{}
の方を使うべきなのですが、うまく参照できませんでした・・・。
ちょっと気になること
結局の所、Object→StringのマッピングをJava側からMybatis側に移すことがこの記事の目的なので達成はしました。
しかしながら、呼び出す側でmapperを準備しているので、完全に処理を移譲したとは言えないと思います。もしまたMybatis側の処理内容に変更が加わった場合、Java側も変更しないといけません。変更に強いアーキテクチャーを実装しようとすると、外部接続するものに依存したくはありません。
その点では、Mybatis側でmapperを準備できればよいでしょう。呼び出される側でmapperを準備できれば、いよいよ呼び出す側はデータそのものを渡すだけすみます。その方法を発見できれば、またアップデートしようと思います。