積年の疑問が晴れた。が、だいぶゴツい。
サマリ
- one-to-many のときは、@One アノテーションに columnPrefix / resultMap を使う
- many-to-many のときは、@Many アノテーションでおなじようにすればできるはず(未検証)
背景
過去、アノテーションベースでは N+1 問題が回避不可だった
しかし、@Oneや@Manyでは上記と同じことを実現できず、必ず他の select を実行するしかありません。
select を利用する方法は N+1 問題が起きてしまうのが嫌で、個人的にはあまり使っていなかったので、ここはちょっとデメリットかなと思いました。
mybatis-3.5.5 で解消した模様
mybatis-3.5.5 で @One
@Many
に columnPrefix を指定できるようになった。
これにより、N + 1 問題を発生させずに、1クエリで one-to-one / one-to-many テーブルをとってこれるようになった。
コード例
こんな感じのテーブルのとき
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY,
username text NOT NULL UNIQUE,
);
CREATE TABLE IF NOT EXISTS articles (
id UUID PRIMARY KEY,
user_id UUID NOT NULL references users(id), -- one-to-one relationship
body text NOT NULL,
);
こうする
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface ArticleRepository {
@Select("""
SELECT
a.id
, a.user_id
, a.body
, u.id as user_id
, u.username as user_username
FROM articles a
JOIN users u on a.user_id = u.id
""")
@Results(value = {
@Result(id = true, column = "id", property = "id"),
@Result(column = "body", property = "body"),
@Result(property = "user", one = @One(resultMap = "userResultMap", columnPrefix = "user_"))
})
List<ArticleRecord> select();
@Select("SELECT '1'") // このクエリは実行されない。@ResultMap を定義するためのダミークエリ
@Results(id = "userResultMap", value = {
@Result(id = true, column = "id", property = "id"),
@Result(column = "username", property = "username"),
})
UserRecord __userResultMap();
}