Загрузка нескольких объектов по id эффективно в HIbernate 4
Итак, я получаю несколько экземпляров определенного объекта с помощью id
for(Integer songId:songGroup.getSongIds())
{
session = HibernateUtil.getSession();
Song song = (Song) session.get(Song.class,id);
processSong(song);
}
это генерирует SQL-запрос для каждого идентификатора, поэтому мне казалось, что я должен сделать это в одном, но я не мог найти способ получить несколько объектов за один вызов, кроме как запустив запрос. Поэтому я написал запрос
return (List) session.createCriteria(Song.class)
.add(Restrictions.in("id",ids)).list();
но если я разрешаю кэширование второго уровня, это не означает, что мой старый метод сможет вернуть объекты из кеша второго уровня (если они были запрошены ранее), но мой запрос всегда будет поступать в базу данных.
Каков правильный способ сделать это?
Ответы
Ответ 1
То, что вы просите сделать здесь, - это Hibernate, чтобы делать специальную обработку дела для ваших критериев, что очень много, чтобы спросить.
Вам нужно будет сделать это самостоятельно, но это не сложно. Используя 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
.
Ответ 2
Если вы знаете, что идентификаторы существуют, вы можете использовать load (..) для создания прокси-сервера без фактического попадания в БД:
Возвращает постоянный экземпляр данного класса сущности с данным идентификатором, получая указанный режим блокировки, предполагая, что экземпляр существует.
List<Song> list = new ArrayList<>(ids.size());
for (Integer id : ids)
list.add(session.load(Song.class, id, LockOptions.NONE));
После того, как вы получите доступ к неидентификатору, Hibernate проверит кэши и вернется в DB, если это необходимо, используя пакетную выборку, если она настроена.
Если идентификатор не существует, объект ObjectNotFoundException будет возникать после загрузки объекта. Это может быть где-то в вашем коде, где вы бы не ожидали исключения - в конце вы используете простой аксессор. Таким образом, либо на 100% убедитесь, что ID существует, либо, по крайней мере, вынуждает ObjectNotFoundException раньше, когда вы ожидаете его, например. сразу после заполнения списка.
Ответ 3
Существует разница между спящим кэшем второго уровня для спящего кэша запросов.
Следующая ссылка очень хорошо объясняет: http://www.javalobby.org/java/forums/t48846.html
В двух словах,
Если вы используете один и тот же запрос много раз с теми же параметрами, вы можете уменьшить количество обращений к базе данных, используя комбинацию обоих.
Ответ 4
Еще одна вещь, которую вы можете сделать, - сортировать список идентификаторов и идентифицировать подпоследовательности последовательных идентификаторов, а затем запрашивать каждую из этих подпоследовательностей в одном запросе. Например, при задании List<Long> ids
выполните следующие действия (предполагая, что у вас есть класс Pair в 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());
}