在HIbernate 4中有效地通过id加载多个实体

时间:2012-02-23 17:37:00

标签: hibernate

所以我通过id

获得了一个特定实体的实例
for(Integer songId:songGroup.getSongIds())
{
   session = HibernateUtil.getSession();
   Song song = (Song) session.get(Song.class,id);
   processSong(song);
}

这会为每个id生成一个sql查询,因此我觉得我应该在一个中执行此操作,但是除了运行查询之外,我找不到在一次调用中获取多个实体的方法。所以我写了一个查询

return (List) session.createCriteria(Song.class)
       .add(Restrictions.in("id",ids)).list();

但如果我启用第二级缓存并不意味着我的旧方法能够从二级缓存返回对象(如果之前已经请求过),但我的查询将始终转到数据库。 / p>

这样做的正确方法是什么?

5 个答案:

答案 0 :(得分:9)

你在这里要做的是让Hibernate为你的Criteria做一些特殊的案件处理,这是一个很多问题。

你必须自己做,但这并不难。使用SessionFactory.getCache(),您可以获得对缓存对象的实际存储的引用。做类似以下的事情:

for (Long id : allRequiredIds) {
  if (!sessionFactory.getCache().containsEntity(Song.class, id)) {
    idsToQueryDatabaseFor.add(id)
  } else {
    songs.add(session.get(Song.class, id));
  }
}

List<Song> fetchedSongs = session.createCriteria(Song.class).add(Restrictions.in("id",idsToQueryDatabaseFor).list();
songs.addAll(fetchedSongs);

然后从那里检索高速缓存中的歌曲,并且用一个select来获取那些未被抓取的歌曲。

答案 1 :(得分:1)

如果您知道ID存在,则可以使用load(..)创建代理而无需实际访问数据库:

  

返回具有给定标识符的给定实体类的持久化实例,获取指定的锁定模式,假设该实例存在。

List<Song> list = new ArrayList<>(ids.size());
for (Integer id : ids)
  list.add(session.load(Song.class, id, LockOptions.NONE));

一旦您访问了非标识符访问者,Hibernate将根据需要检查缓存并回退到数据库,如果已配置,则使用批量获取。

如果ID不存在,则加载对象后将发生ObjectNotFoundException。这可能在您的代码中某处,您不会真正期待异常 - 您最终会使用简单的访问者。因此要么100%确定ID存在,要么至少在您期望的情况下强制使用ObjectNotFoundException,例如:填写清单后立即。

答案 2 :(得分:0)

hibernate二级缓存与休眠查询缓存之间存在差异。 以下链接解释得非常好:http://www.javalobby.org/java/forums/t48846.html

简而言之, 如果您使用相同的参数多次使用相同的查询,则可以使用两者的组合来减少数据库命中。

答案 3 :(得分:0)

您可以做的另一件事是对id列表进行排序,并确定连续ID的子序列,然后在单个查询中查询每个子序列。例如,给定List<Long> ids,执行以下操作(假设您在Java中有Pair类):

List<Pair> pairs=new LinkedList<Pair>();
List<Object> results=new LinkedList<Object>();
Collections.sort(ids);
Iterator<Long> it=ids.iterator();

Long previous=-1L;
Long sequence_start=-1L;
while (it.hasNext()){
    Long next=it.next();

    if (next>previous+1) {
        pairs.add(new Pair(sequence_start, previous));
        sequence_start=next;
    }
    previous=next;
}
pairs.add(new Pair(sequence_start, previous));

for (Pair pair : pairs){
    Query query=session.createQuery("from Person p where p.id>=:start_id and p.id<=:end_id");
    query.setLong("start_id", pair.getStart());
    query.setLong("end_id", pair.getEnd());

    results.addAll((List<Object>)query.list());

}

答案 4 :(得分:0)

在一个循环中逐个获取每个实体可能会导致N+1 query issues

因此,一次获取所有实体并随后进行处理要高效得多。

现在,在您提出的解决方案中,您正在使用旧的Hibernate Criteria,但由于自Hibernate 4起已弃用该标准,并且可能会在Hibernate 6中将其删除,因此最好使用以下替代方法之一。

JPQL

您可以使用JPQL查询,如下所示:

List<Song> songs = entityManager
.createQuery(
    "select s " +
    "from Song s " +
    "where s.id in (:ids)", Song.class)
.setParameter("ids", songGroup.getSongIds())
.getResultList();

标准API

如果要动态构建查询,则可以使用Criteria API查询:

CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Song> query = builder.createQuery(Song.class);

ParameterExpression<List> ids = builder.parameter(List.class);

Root<Song> root = query
.from(Song.class);

query
.where(
    root.get("id").in(
        ids
    )
);

List<Song> songs = entityManager
.createQuery(query)
.setParameter(ids, songGroup.getSongIds())
.getResultList();

休眠特定的multiLoad

List<Song> songs = entityManager
.unwrap(Session.class)
.byMultipleIds(Song.class)
.multiLoad(songGroup.getSongIds());

现在,JPQL和Criteria API也可以从hibernate.query.in_clause_parameter_padding optimization中受益,这使您可以增强SQL语句缓存机制。

有关通过其标识符加载多个实体的更多详细信息,请查看this article