Top > MyPage
 

hibernateの関連とFETCHについて(メモ)

FETCH

Hibernateのエンティティ間の関連とデータの取り方について説明する。

例に使用するテーブルとエンティティは「hibernateの双方向many-to-manyについて」と同じものを使うので、そちらを参照してほしい。

なお、検証にはこの時点のhibernateの最新バージョン(3.3.1.GA)をもちいた。

関連があるエンティティの取得

Usersクラスを取得する際に、関連のあるRolesがどのように取得されるのかを例を挙げて説明する。

特に何もせずUsersを取得

例えば、次のような、至極単純なHQLを記述して実行したとする。

    FROM Users AS u WHERE u.id='5' ORDER BY u.usersId ASC

すると、当然USERSテーブルの全てのレコードを読み出して、UsersのListとして返されるわけだが、マッピングファイルでrolesに対してlazy=falseを指定しているため、ROLESテーブルのレコードも読み出して、Usersにセットされる。

そのとき発行されるsqlは次のようになる。(Roles側からのUsersの取得は省略)

     Hibernate: select users0_.USERS_ID as USERS1_0_, users0_.USERNAME as USERNAME0_,
                users0_.PASSWORD as PASSWORD0_ 
                from USERS users0_ where users0_.USERS_ID=5 order by users0_.USERS_ID ASC
                
     Hibernate: select roles0_.USERS_ID as USERS1_1_, roles0_.ROLES_ID as ROLES2_1_,
                roles1_.ROLES_ID as ROLES1_2_0_, roles1_.ROLE_NAME as ROLE2_2_0_ 
                from USER_ROLE roles0_
                left outer join ROLES roles1_ on roles0_.ROLES_ID=roles1_.ROLES_ID 
                where roles0_.USERS_ID=?

先にUSERSテーブルのデータを取得した後で、別のselect文を発行してROLESのデータを取得していることがわかる。lazy=falseであるから、先のsqlの直後にROLESから取得しているが、lazy=trueであった場合は、この後の方のsqlはUsers::getRoles()がコールされたときに実行されるのである。

これは、one-to-manyである場合も同様のことがおこる。

FETCHを指定

先ほどのHQLに少し書きくわえて、次のようにして、実行したとする。

    SELECT u FROM Users AS u JOIN FETCH u.roles AS roles WHERE u.id='5' ORDER BY u.usersId ASC

先ほどのものとの違いは、SELECT句とJOIN句があることである。JOINを加えたことで、取得するべきエンティティを特定できなくなったので、select句でuを明示的に指定している。ポイントはJOINの後ろに書いているである。これを書くことにより、Rolesの取得の仕方が変わるのである。このFETCHがないと先ほどのものと、同様の結果になる。発行されたsqlは次のようになる。

          Hibernate: select users0_.USERS_ID as USERS1_0_0_, roles2_.ROLES_ID as ROLES1_2_1_,
                     users0_.USERNAME as USERNAME0_0_, users0_.PASSWORD as PASSWORD0_0_,
                     roles2_.ROLE_NAME as ROLE2_2_1_, roles1_.USERS_ID as USERS1_0__,
                     roles1_.ROLES_ID as ROLES2_0__ 
                     from USERS users0_ 
                     inner join USER_ROLE roles1_ 
                     on users0_.USERS_ID=roles1_.USERS_ID
                     inner join ROLES roles2_ 
                     on roles1_.ROLES_ID=roles2_.ROLES_ID 
                     where users0_.USERS_ID='5' order by users0_.USERS_ID ASC

先ほどの例ではRolesは後から別のsqlを発行して取得していたが、今度は、最初のsqlでinner joinして一緒に取得している。発行されたSQLからわかるようにinner joinであるため、Rolesと関連を持たないUsersは取得されないことになる。

そこで、HQLにさらに少し書き加えて次のようにする。

    SELECT u FROM Users AS u OUTER JOIN FETCH u.roles AS roles WHERE u.id='5' ORDER BY u.usersId ASC

すると、sqlでは「inner join」ではなく、「left outer join」になる。

この方法では、一回のsql発行でデータを持ってくるので早いように見えるが、joinしているテーブルのレコードの数だけUSERSのデータも取得するのでUSERSのカラム数が多い場合、関連しているROLESのレコードが多い場合は、無駄なデータの取得も多くなるので、先にUSERSのデータを取得してから、対応するROLESのデータを取得する方が早いかもしれない。

どちらを使うべきか、よく考えるとよいだろう。