Подтвердить что ты не робот

Как я могу избежать предупреждения "firstResult/maxResults, указанных при получении коллекции; применение в памяти!" при использовании Hibernate?

Я получаю предупреждение в журнале сервера "firstResult/maxResults, указанное с помощью коллекции fetch; применение в памяти!" . Однако все работает нормально. Но я не хочу этого предупреждения.

Мой код

public employee find(int id) {
    return (employee) getEntityManager().createQuery(QUERY).setParameter("id", id).getSingleResult();
}

Мой запрос

QUERY = "from employee as emp left join fetch emp.salary left join fetch emp.department where emp.id = :id"
4b9b3361

Ответ 1

Причиной этого предупреждения является то, что при использовании соединения fetch порядок в наборах результатов определяется только идентификатором выбранного объекта (а не путем присоединения).

Если эта сортировка в памяти вызывает проблемы, не используйте firsResult/maxResults с JOIN FETCH.

Ответ 2

Чтобы избежать этого ПРЕДУПРЕЖДЕНИЯ, вы должны изменить вызов getSingleResult на getResultList().get(0)

Ответ 3

Несмотря на то, что вы получаете достоверные результаты, SQL-запрос извлекает все данные, и он не так эффективен, как следовало бы.

Как я объяснил в этой статье, у вас есть два варианта.

Исправление проблемы с двумя запросами SQL, которые могут извлекать объекты в режиме чтения-записи

Самый простой способ решить эту проблему - выполнить два запроса:

Первый запрос извлекает идентификаторы корневого объекта, соответствующие предоставленным критериям фильтрации. , Второй запрос будет использовать ранее извлеченные идентификаторы корневых объектов для извлечения родительских и дочерних объектов.

Этот подход очень прост в реализации и выглядит следующим образом:

List<Long> postIds = entityManager
.createQuery(
    "select p.id " +
    "from Post p " +
    "where p.title like :titlePattern " +
    "order by p.createdOn", Long.class)
.setParameter(
    "titlePattern",
    "High-Performance Java Persistence %"
)
.setMaxResults(5)
.getResultList();

List<Post> posts = entityManager
.createQuery(
    "select distinct p " +
    "from Post p " +
    "left join fetch p.comments " +
    "where p.id in (:postIds)", Post.class)
.setParameter("postIds", postIds)
.setHint(
    QueryHints.HINT_PASS_DISTINCT_THROUGH,
    false
)
.getResultList();

Исправление проблемы с одним запросом SQL, который может извлекать объекты только в режиме только для чтения

Второй подход заключается в использовании SDENSE_RANK для набора результатов родительских и дочерних объектов, которые соответствуют нашим критериям фильтрации и ограничивают вывод только для первых N записей записей.

Запрос SQL может выглядеть следующим образом:

@NamedNativeQuery(
    name = "PostWithCommentByRank",
    query =
        "SELECT * " +
        "FROM (   " +
        "    SELECT *, dense_rank() OVER (ORDER BY \"p.created_on\", \"p.id\") rank " +
        "    FROM (   " +
        "        SELECT p.id AS \"p.id\", " +
        "               p.created_on AS \"p.created_on\", " +
        "               p.title AS \"p.title\", " +
        "               pc.id as \"pc.id\", " +
        "               pc.created_on AS \"pc.created_on\", " +
        "               pc.review AS \"pc.review\", " +
        "               pc.post_id AS \"pc.post_id\" " +
        "        FROM post p  " +
        "        LEFT JOIN post_comment pc ON p.id = pc.post_id " +
        "        WHERE p.title LIKE :titlePattern " +
        "        ORDER BY p.created_on " +
        "    ) p_pc " +
        ") p_pc_r " +
        "WHERE p_pc_r.rank <= :rank ",
    resultSetMapping = "PostWithCommentByRankMapping"
)
@SqlResultSetMapping(
    name = "PostWithCommentByRankMapping",
    entities = {
        @EntityResult(
            entityClass = Post.class,
            fields = {
                @FieldResult(name = "id", column = "p.id"),
                @FieldResult(name = "createdOn", column = "p.created_on"),
                @FieldResult(name = "title", column = "p.title"),
            }
        ),
        @EntityResult(
            entityClass = PostComment.class,
            fields = {
                @FieldResult(name = "id", column = "pc.id"),
                @FieldResult(name = "createdOn", column = "pc.created_on"),
                @FieldResult(name = "review", column = "pc.review"),
                @FieldResult(name = "post", column = "pc.post_id"),
            }
        )
    }
)

@NamedNativeQuery выбирает все сущности Post, соответствующие предоставленному заголовку, вместе со связанными с ними дочерними сущностями PostComment. Оконная функция DENSE_RANK используется для назначения ранга каждой объединенной записи Post и PostComment чтобы впоследствии мы могли отфильтровать только то количество записей Post, которые нам интересны.

SqlResultSetMapping обеспечивает сопоставление между псевдонимами столбцов уровня SQL и свойствами сущностей JPA, которые необходимо заполнить.

Теперь мы можем выполнить PostWithCommentByRank @NamedNativeQuery следующим образом:

List<Post> posts = entityManager
.createNamedQuery("PostWithCommentByRank")
.setParameter(
    "titlePattern",
    "High-Performance Java Persistence %"
)
.setParameter(
    "rank",
    5
)
.unwrap(NativeQuery.class)
.setResultTransformer(
    new DistinctPostResultTransformer(entityManager)
)
.getResultList();

Теперь, по умолчанию, собственный SQL-запрос, такой как PostWithCommentByRank будет PostWithCommentByRank Post и PostComment в одной и той же строке JDBC, поэтому мы получим Object [], содержащий обе сущности.

Однако мы хотим преобразовать табличный массив Object[] в дерево родительских и дочерних объектов, и по этой причине нам нужно использовать Hibernate ResultTransformer Для получения дополнительной информации о ResultTransformer ознакомьтесь с этой статьей.

DistinctPostResultTransformer выглядит следующим образом:

public class DistinctPostResultTransformer
        extends BasicTransformerAdapter {

    private final EntityManager entityManager;

    public DistinctPostResultTransformer(
            EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    @Override
    public List transformList(
            List list) {

        Map<Serializable, Identifiable> identifiableMap =
            new LinkedHashMap<>(list.size());

        for (Object entityArray : list) {
            if (Object[].class.isAssignableFrom(entityArray.getClass())) {
                Post post = null;
                PostComment comment = null;

                Object[] tuples = (Object[]) entityArray;

                for (Object tuple : tuples) {
                    if(tuple instanceof Identifiable) {
                        entityManager.detach(tuple);

                        if (tuple instanceof Post) {
                            post = (Post) tuple;
                        }
                        else if (tuple instanceof PostComment) {
                            comment = (PostComment) tuple;
                        }
                        else {
                            throw new UnsupportedOperationException(
                                "Tuple " + tuple.getClass() + " is not supported!"
                            );
                        }
                    }
                }

                if (post != null) {
                    if (!identifiableMap.containsKey(post.getId())) {
                        identifiableMap.put(post.getId(), post);
                        post.setComments(new ArrayList<>());
                    }
                    if (comment != null) {
                        post.addComment(comment);
                    }
                }
            }
        }
        return new ArrayList<>(identifiableMap.values());
    }
}

DistinctPostResultTransformer должен отсоединить извлекаемые сущности, потому что мы перезаписываем дочернюю коллекцию и не хотим, чтобы это передавалось как переход состояния сущности:

post.setComments(new ArrayList<>());

Для более подробной информации, ознакомьтесь с этой статьей.

Ответ 4

Проблема в том, что вы получите декартовский продукт, выполняющий JOIN. Смещение сократит ваш набор записей, не глядя, если вы все еще находитесь в том же корневом классе идентификации

Ответ 5

Я думаю, у emp есть много отделов, которые являются отношениями от одного до многих. Hibernate будет извлекать много строк для этого запроса с извлеченными записями отдела. Таким образом, порядок набора результатов не может быть определен до тех пор, пока он действительно не получит результаты в память. Таким образом, разбиение на страницы будет выполнено в памяти.

Если вы не хотите получать департаменты с помощью emp, но все же хотите сделать какой-то запрос на основе отдела, вы можете добиться результата без предупреждения (без упорядочения в памяти). Для этого вам просто нужно удалить предложение "выборка". Итак, что-то вроде следующего:

QUERY = "от сотрудника как emp left join emp.salary sal left join emp.department dep где emp.id =: id и dep.name = 'testing' и sal.salary > 5000"