在 Hibernate 中通过 id 高效加载多个实体

发布于 2025-01-08 18:24:35 字数 516 浏览 0 评论 0原文

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

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

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

So, I'm getting a number of instances of a particular entity by id:

for(Integer songId:songGroup.getSongIds()) {
   session = HibernateUtil.getSession();
   Song song = (Song) session.get(Song.class,id);
   processSong(song);
}

This generates a SQL query for each id, so it occurred to me that I should do this in one, but I couldn't find a way to get multiple entities in one call except by running a query. So I wrote a query

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

But, if I enable 2nd level caching doesn't that mean that my old method would be able to return the objects from the 2nd level cache (if they had been requested before) but my query would always go to the database.

What the correct way to do this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

南…巷孤猫 2025-01-15 18:24:35

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

然后从缓存中检索歌曲,而未通过单个选择提取的歌曲。

What you're asking to do here is for Hibernate to do special case handling for your Criteria, which is kind of a lot to ask.

You'll have to do it yourself, but it's not hard. Using SessionFactory.getCache(), you can get a reference to the actual storage for cached objects. Do something like the following:

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);

Then the Songs from the cache get retrieved from there, and the ones that are not get pulled with a single select.

御守 2025-01-15 18:24:35

如果您知道 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,例如在填充列表后立即执行。

If you know that the IDs exist, you can use load(..) to create a proxy without actually hitting the DB:

Return the persistent instance of the given entity class with the given identifier, obtaining the specified lock mode, assuming the instance exists.

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

Once you access a non-identifier accessor, Hibernate will check the caches and fallback to DB if needed, using batch-fetching if configured.

If the ID doesn't exists, a ObjectNotFoundException will occur once the object is loaded. This might be somewhere in your code where you wouldn't really expect an exception - you're using a simple accessor in the end. So either be 100% sure the ID exists or at least force a ObjectNotFoundException early where you'd expect it, e.g. right after populating the list.

内心荒芜 2025-01-15 18:24:35

Hibernate 二级缓存与 Hibernate 查询缓存之间存在差异。
以下链接对此进行了很好的解释: http://www.javalobby.org/java/forums /t48846.html

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

There is a difference between hibernate 2nd level cache to hibernate query cache.
The following link explains it really well: http://www.javalobby.org/java/forums/t48846.html

In a nutshell,
If you are using the same query many times with the same parameters then you can reduce database hits using a combination of both.

故事与诗 2025-01-15 18:24:35

您可以做的另一件事是对 id 列表进行排序,并识别连续 id 的子序列,然后在单个查询中查询每个子序列。例如,给定Listids,执行以下操作(假设您有 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());

}

Another thing that you could do is to sort the list of ids, and identify subsequences of consecutive ids and then query each of those subsequences in a single query. For example, given List<Long> ids, do the following (assuming that you have a Pair class in Java):

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());

}
陌生 2025-01-15 18:24:35

在循环中逐一获取每个实体可能会导致 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();

Criteria 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语句缓存机制。

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

Fetching each entity one by one in a loop can lead to N+1 query issues.

Therefore, it's much more efficient to fetch all entities at once and do the processing afterward.

Now, in your proposed solution, you were using the legacy Hibernate Criteria, but since it's been deprecated since Hibernate 4 and will probably be removed in Hibernate 6, so it's better to use one of the following alternatives.

JPQL

You can use a JPQL query like the following one:

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

Criteria API

If you want to build the query dynamically, then you can use a Criteria API query:

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-specific multiLoad

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

Now, the JPQL and Criteria API can benefit from the hibernate.query.in_clause_parameter_padding optimization as well, which allows you to increase the SQL statement caching mechanism.

For more details about loading multiple entities by their identifier, check out this article.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文