scalikeJDBC One-to-X APIを使いこなす(1:N:N編)


scalikeJDBC One-to-X APIを使いこなす(基本編)の続編です。
今回は1:N:Nのテーブルの扱い方についてです。

One-to-X API Document をみるとtoManiesで1:N:Nの取得ができそうですが、そこには落とし穴があり・・・😱
3つのテーブルが連なっている形の1:N:Nの場合はgroupByをしないと意図しない形でデータを扱ってしまう可能性があり危険です。

まずシチュエーションの確認からしたいと思います。

こういう関係の3つのテーブルがあったとします。

これを、以下のように表示したいとします。

企業によっては、事業部とユーザーの登録をしていないかもしれないので、こういったブランクの表示二なるパターンもあります。

このとき、DBから取得した値は👇のように、
企業に複数の事業部がひもづいていて、事業部に対して複数のユーザーが紐づいて欲しいですね。

これをtoManiesで書いてみます🎉

withSQL[Corporates] {
  select
    .from(Corporates.as(corporatesTable))
    .leftJoin(Departments.as(departmentsTable))
    .on(corporatesTable.corporateId, departmentsTable.corporateId)
    .leftJoin(Users.as(usersTable))
    .on(departmentsTable.departmentId, usersTable.departmentId)
}.one(Corporates(corporatesTable))
  .toManies(
    rs => rs.longOpt(departmentsTable.resultName.corporateId).map(_ => Departments(departmentsTable)(rs)),
    rs => rs.longOpt(usersTable.resultName.departmentId).map(_ => Users(usersTable)(rs)),
  )
  .map((corporate, departments, users) => (corporate, departments, users))
  .list()
  .apply()

前回の記事 を参考に書くと、こういう感じになります。
ここで、この行に注目してください、

.map((corporate, departments, users) => (corporate, departments, users))

怪しさMAXですね。
これは図で表すと、こういう形で取得されています。

この時点では、ある企業のユーザーは、どの事業部に紐づているのかを表せていません・・・😱
なので、ユーザーを事業部IDでグルーピングしてあげる必要があります。
もしそれを考慮せずに👇このようにしてしまうと、ある企業のすべての事業部に同じユーザーが所属しているような表示になってしまいます。
グルーピングを忘れるだけで、大惨事を起こしかねません。

.map((corporate, departments, users) => 
CorporatesModel(corporate.name, departments.map(department => DepartmensModel(department.name, users.map(user => UsersModel(user.name)))))

グルーピングをするためには、このように書くのが良さそうです。

  ...
  .map((corporate, departments, users) =>
     val groupedUsers: Map[Long, Seq[Users]] = users.groupBy(user => user.departmentId)
     CorporatesModel(corporate.name, departments.map(department => DepartmensModel(department.name), groupedUsers(departments.departmentId).map(user => UsersModel(user.name)))  
  )
 ...

groupByを使ってusersをMAP型にし、departmentIdで束ねてある形にします。
これで、ある事業部にユーザーが紐づいている、という正しい形にマッピングすることができます👏
そして、正しいタイミングで事業部IDを元にユーザーのリストを取得すれば欲しいモデルの形に変換することができました。

1:NであればscalikeJDBC内で対応でき流のでグルーピングの考慮は不要ですが、
1:N:N...だと考慮が必要であり、このように実装するのがベストだと思っています。
(※もしscalikeDJBCのコード内で同じことが表現できるのであれば知りたいです・・・)