🌻
Doma で many-to-many 型のリレーションシップを扱う(Criteria API版)
この記事では、Doma の Criteria API を使って many-to-many 型のリレーションシップを扱う方法を紹介します。
SQLを使ってmany-to-many 型のリレーションシップを扱う方法は以下の記事を参照ください。
DDL と初期データ
例えば、users、roles、users_roles の 3 つのテーブルがあるとしましょう。1 人のユーザーが複数のロールを持つことができ、1 つのロールが複数のユーザーに割り当てられることがある、というケースです(冒頭で紹介した記事で使ったものと同じです。)
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
CREATE TABLE roles (
id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
CREATE TABLE users_roles (
user_id INT NOT NULL,
role_id INT NOT NULL,
PRIMARY KEY (user_id, role_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
);
INSERT INTO users (id, name) VALUES (1, 'Alice'), (2, 'Bob');
INSERT INTO roles (id, name) VALUES (1, 'Admin'), (2, 'Editor'), (3, 'Viewer');
-- Alice は Admin と Editor の両方のロールを持つ
INSERT INTO users_roles (user_id, role_id) VALUES (1, 1), (1, 2);
-- Bob は Editor と Viewer の両方のロールを持つ
INSERT INTO users_roles (user_id, role_id) VALUES (2, 2), (2, 3);
エンティティクラス
users
、roles
、users_roles
の3つのテーブルに対応するエンティティクラスを作成します。
@Entity(metamodel = @Metamodel)
@Table(name = "users")
public class User {
@Id public Integer id;
public String name;
@Association public List<Role> roles = new ArrayList<>();
}
@Entity(metamodel = @Metamodel)
@Table(name = "roles")
public class Role {
@Id public Integer id;
public String name;
@Association public List<User> users = new ArrayList<>();
}
@Entity(metamodel = @Metamodel)
@Table(name = "users_roles")
public class UserRole {
@Id public Integer userId;
@Id public Integer roleId;
}
Criteira API のメタモデルを生成するために metamodel = @Metamodel
の指定は必須です。
実行例
JUnitのテストから実行してみましょう。
@Test
public void manyToManyWithCriteriaApi(Config config) {
QueryDsl queryDsl = new QueryDsl(config);
// メタモデルをインスタンス化
User_ u = new User_();
Role_ r = new Role_();
UserRole_ ur = new UserRole_();
// クエリを構築して実行
List<User> users =
queryDsl
.from(u)
.leftJoin(ur, on -> on.eq(u.id, ur.userId))
.leftJoin(r, on -> on.eq(ur.roleId, r.id))
.orderBy(o -> o.asc(u.id))
.associate(
u,
r,
(user, role) -> {
user.roles.add(role);
role.users.add(user);
})
.fetch();
// 結果を出力
for (User user : users) {
String userName = user.name;
String roleNames =
user.roles.stream().map(role -> role.name).collect(Collectors.joining(","));
System.out.printf("user=%s, roles=%s%n", userName, roleNames);
}
}
出力結果は次のようになります。
user=Alice, roles=Admin,Editor
user=Bob, roles=Editor,Viewer
発行される SQL は次のとおりです。
select
t0_.ID, t0_.NAME, t2_.ID, t2_.NAME
from
users t0_
left outer join
users_roles t1_ on (t0_.ID = t1_.USER_ID)
left outer join
roles t2_ on (t1_.ROLE_ID = t2_.ID)
order by t0_.ID asc
まとめ
Doma の Criteria API を使って many-to-many 型のリレーションシップを扱う例を紹介しました。
冒頭で紹介したように SQL を使っても同じことができます。SQL を使う方法は、SELECT リストの命名規則さえあっていればアグリゲートにマッピング可能なので、DBに特化した関数や複雑なサブクエリが必要な場合には向いているでしょう。
なお、SQL を使う方法と Criteria API を使う方法は混在しても全く問題ありません(例えば、JPAのように適切なタイミングでflushが必要などの注意点はありません)。システムやプロジェクトの特性に応じて適材適所で使ってもらえればと思います。
Discussion