В Hibernate StatelessSession предотвращает отфильтровывать дубликаты, когда есть EAGER JOIN - PullRequest
3 голосов
/ 10 июня 2019

У меня есть класс Song , содержащий коллекцию CoverArt s

1007 * например *

@OneToMany(fetch=FetchType.LAZY, cascade={CascadeType.ALL})
@JoinColumn(name = "recNo")
private List<CoverArt> coverArts;

и я использую Hibernate 4.3.11 и базу данных DB2, и у меня есть этот запрос для получения списка песен по их первичному ключу вместе с их coverArt.

public static List<Song> getSongsWithCoverArtFromDatabase(Session session, List<Integer> ids)
    {
        try
        {
            Criteria c = session
                    .createCriteria(Song.class)
                    .setFetchMode("coverArts", FetchMode.JOIN)
                    .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
                    .add(Restrictions.in("recNo", ids));
            List<Song> songs = c.list();
            return songs;
        }
        catch (Exception e)
        {
            MainWindow.logger.log(Level.SEVERE, "Failed LoadSongToDatabase:" + e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }

Обратите внимание, что мы установили режим выборки на JOIN в коллекции coverArts и что нам нужно установить s etResultTransformer (Criteria.DISTINCT_ROOT_ENTITY) , в противном случае, если у нас была песня с двумя общими записями, мы вернули бы два объекта Song обратно. Но при использовании Criteria.DISTINCT_ROOT_ENTITY Hibernate правильно вернул бы одну композицию, содержащую два coverArts.

Однако я только что попытался сделать то же самое, но с использованием StatelessSession. Причина в том, что я просто пытаюсь выбрать данные для создания отчета, и я хочу максимизировать скорость и минимизировать потребление памяти, однако

   public static List<Song> getSongsWithCoverArtFromDatabase(StatelessSession session, List<Integer> ids)
    {
        try
        {
            Criteria c = session
                    .createCriteria(Song.class)
                    .setFetchMode("coverArts", FetchMode.JOIN)
                    .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
                    .add(Restrictions.in("recNo", ids));
            List<Song> songs = c.list();
            return songs;
        }
        catch (Exception e)
        {
            MainWindow.logger.log(Level.SEVERE, "Failed LoadSongToDatabase:" + e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }

это, кажется, игнорирует .setResultTransformer (Criteria.DISTINCT_ROOT_ENTITY) и возвращает дублирующиеся строки.

Это известная ошибка, как она должна себя вести?

1 Ответ

2 голосов
/ 16 июня 2019

Похоже, это недостаток в том, что StatelessSessionImpl реализован в Hibernate, но исправление может быть и в пути ...

Очевидно, что с FetchMode.JOIN запрос SQL будет (слева) объединяет две таблицы, поэтому может возвращать несколько строк на песню.Обычно Hibernate разрешает каждую строку, возвращаемую через PersistenceContext.

Если интересно, вы можете увидеть это в исходном коде Hibernate для Loader здесь .Затем, в зависимости от типа Session, SessionImpl.getEntityUsingInterceptor () связывается с PersistenceContext, но StatelessSessionImpl.getEntityUsingInterceptor () просто возвращает ноль.Однако есть более поздняя фиксация для этого метода, которая, похоже, делает все правильно.Этот коммит является частью HHH-11147 , в котором говорится, что версиями исправлений являются Hibernate 5.3.11 и 5.4.4 - не показывается в репозитории Maven на момент написания.

Между тем, одним из исправлений было бы бросить свой собственный ResultTransformer.Это довольно точный пример:

public class DistinctSongResultTransformer implements ResultTransformer {
    private ResultTransformer defaultTransformer = Criteria.DISTINCT_ROOT_ENTITY;

    @Override
    public Object transformTuple(Object[] tuple, String[] aliases) {
        return defaultTransformer.transformTuple(tuple, aliases);
    }

    @SuppressWarnings("rawtypes")
    @Override
    public List transformList(List collection) {
        Map<Integer, Song> distinctSongs = new LinkedHashMap<>();
        for (Object object : collection) {
            Song song = (Song) object;
            distinctSongs.putIfAbsent(song.getId(), song);
        }
        return new ArrayList<>(distinctSongs.values());
    }
}

Разница в том, что обычный DistinctRootEntityResultTransformer предполагает, что в сеансе будет только уникальный экземпляр объекта - вы можете увидеть сравнение здесь .

Очевидно, что есть возможность сделать этот пример более пригодным для повторного использования, особенно для абстрагирования getId().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...