16

因此,我通过 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();

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

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

4

5 回答 5

10

您在这里要求做的是让 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.

于 2013-06-27T17:00:19.707 回答
1

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

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

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

一旦您访问了非标识符访问器,Hibernate 将检查缓存并在需要时回退到 DB,如果配置则使用批量获取。

如果 ID 不存在,则在加载对象后会发生 ObjectNotFoundException。这可能在您的代码中您不会真正期望异常的某个地方 - 您最终使用的是一个简单的访问器。因此,要么 100% 确定 ID 存在,要么至少在您期望它的早期强制一个 ObjectNotFoundException,例如在填充列表之后。

于 2015-04-20T09:33:58.223 回答
0

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

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

于 2013-06-24T12:28:01.100 回答
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());

}
于 2014-09-05T15:21:35.903 回答
0

在循环中一个一个地获取每个实体可能会导致N+1 个查询问题

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

现在,在您提出的解决方案中,您使用的是传统的 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();

Hibernate 特定的 multiLoad

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

现在,JPQL 和 Criteria API 也可以从hibernate.query.in_clause_parameter_padding优化中受益,这允许您增加 SQL 语句缓存机制。

有关按标识符加载多个实体的更多详细信息,请查看这篇文章

于 2019-10-25T06:33:29.653 回答