join抓取两个以上的平行集合会产生笛卡尔积,而如果使用subselect抓取又会失去动态抓取集合其他依赖对象的机会。要怎样才能动态地抓取平行集合呢?目前我能想到的一种方法就是生成多条select,分别抓取平行集合。我觉得这并不是一个很完美的方案,但是它却实是可行的。
以Forum为例,在某个use case中我们需要加载一个Forum,同时要显示它的所有Thread以及Moderator.Forum的类代码为:
@Entity @Table public class Forum implements ForumNode,Serializable{ @ManyToMany private Set<User> moderators; @OneToMany private List<Thread> threads = new ArrayList<Thread>(); }
根据ID加载一个Forum,并将这两个集合抓取出来的方法是:
public Forum getForumWithModeratorsAndThreads(final long forumId){ return (Forum) getHibernateTemplate().executeWithNativeSession(new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { //Get forum. String getForuumHql= "from Forum as forum where forum.id=:forumId"; Forum forum = (Forum) session.createQuery(getForuumHql).setParameter("forumId", forumId).uniqueResult(); //Fetch threads. String fetchForumThreadsHql = "from Forum as forum left join fetch forum.threads as thread left join fetch thread.subject where forum=:forum"; session.createQuery(fetchForumThreadsHql).setParameter("forum", forum).list(); //Fetch moderators. String fetchForumMorderatorsHql = "from Forum as forum left join fetch forum.moderators as moderator left join fetch moderator.roles where forum=:forum"; session.createQuery(fetchForumMorderatorsHql).setParameter("forum", forum).list(); logger.info("The Forum (ID="+forumId+") has loaded with moderators and threads"); return forum; } }); }
这段代码做了三个操作,生成三条SQL.第一个操作是加载一个不带任何关联对象的Forum。第二个操作是抓取这个Forum的Thread集合。第三个操作是抓取这个Forum的Moderator集合。
下面是测试代码:
@Test @Transactional public void testgetForumWithModeratorsAndThreads(){ Forum forum = forumRepository.getForumWithModeratorsAndThreads(1L); System.out.println("****************************************************"); System.out.println(forum.getModerators().size()); System.out.println(forum.getThreads().size()); }
测试的输出结果是:
Hibernate:
/*
from
Forum as forum
where
forum.id=:forumId */ select
forum0_.id as id2_,
forum0_.creationTime as creation2_2_,
forum0_.description as descript3_2_,
forum0_.groupId as groupId2_,
forum0_.modifiedTime as modified4_2_,
forum0_.name as name2_
from
Forum forum0_
where
forum0_.id=?
Hibernate:
/*
from
Forum as forum
left join
fetch forum.threads as thread
left join
fetch thread.subject
where
forum=:forum */ select
forum0_.id as id2_0_,
threads1_.id as id5_1_,
post2_.id as id4_2_,
forum0_.creationTime as creation2_2_0_,
forum0_.description as descript3_2_0_,
forum0_.groupId as groupId2_0_,
forum0_.modifiedTime as modified4_2_0_,
forum0_.name as name2_0_,
threads1_.creationTime as creation2_5_1_,
threads1_.forumId as forumId5_1_,
threads1_.modifiedTime as modified3_5_1_,
threads1_1_.subjectId as subjectId6_1_,
threads1_.forumId as forumId0__,
threads1_.id as id0__,
post2_.authorId as authorId4_2_,
post2_.creationTime as creation2_4_2_,
post2_.isSubject as isSubject4_2_,
post2_.messageBody as messageB4_4_2_,
post2_.modifiedTime as modified5_4_2_,
post2_.quotedPostId as quotedPo8_4_2_,
post2_.threadId as threadId4_2_,
post2_.title as title4_2_
from
Forum forum0_
left outer join
Thread threads1_
on forum0_.id=threads1_.forumId
left outer join
Thread_Subject threads1_1_
on threads1_.id=threads1_1_.threadId
left outer join
Post post2_
on threads1_1_.subjectId=post2_.id
where
forum0_.id=?
Hibernate:
/*
from
Forum as forum
left join
fetch forum.moderators as moderator
left join
fetch moderator.roles
where
forum=:forum */ select
forum0_.id as id2_0_,
user2_.id as id0_1_,
role4_.id as id1_2_,
forum0_.creationTime as creation2_2_0_,
forum0_.description as descript3_2_0_,
forum0_.groupId as groupId2_0_,
forum0_.modifiedTime as modified4_2_0_,
forum0_.name as name2_0_,
user2_.accountNonExpired as accountN2_0_1_,
user2_.accountNonLocked as accountN3_0_1_,
user2_.credentialsNonExpired as credenti4_0_1_,
user2_.email as email0_1_,
user2_.enabled as enabled0_1_,
user2_.password as password0_1_,
user2_.username as username0_1_,
user2_.version as version0_1_,
moderators1_.forumId as forumId0__,
moderators1_.moderatorId as moderato2_0__,
role4_.description as descript2_1_2_,
role4_.name as name1_2_,
roles3_.userId as userId1__,
roles3_.roleId as roleId1__
from
Forum forum0_
left outer join
Forum_Moderator moderators1_
on forum0_.id=moderators1_.forumId
left outer join
User user2_
on moderators1_.moderatorId=user2_.id
left outer join
User_Role roles3_
on user2_.id=roles3_.userId
left outer join
Role role4_
on roles3_.roleId=role4_.id
where
forum0_.id=?
INFO - ForumHibernateRepository$1.doInHibernate(38) | The Forum (ID=1) has loaded with moderators and threads
****************************************************
1
2
从运行结果来看,getForumWithModeratorsAndThreads方法返回的Forum是一个已经将moderator和thread抓取出来的Forum实例。当我们在测试代码中访问这两个集合时,并没有任何sql打出,说明它们都已经被抓取出来了。
我觉得这个方案不好的地方在于抓取两个平行集合的hql有些怪异,它们select的是Forum,但实际上我们并不是要这个Forum,而是要抓取它的集合。因此执行些hql后,我们也没有要得到查询结果的意思。另外,每次查询都要从Forum开始进行join也觉得多做一些事情。 通过验证我发现, 如果直接select这个forum的thread和moderator集合,再访问这个forum的这两个集合时,它还是要去查DB,这说明, 直接select它们不会导致forum的 thread和 moderator集合(应该是hibernate的colletcion wapper)被加载。我想原因是因为hibernate无法确认我们通过select查出的这些元素一定是forum的全部thread集合。hibernate必须重新执行一次以确保全部的集合被加载出来。因此目前来说,也只有这种方式还算可行。我想以后可能会找到更好的解决方法吧。