Запрос с проблемой производительности JOIN FETCH - PullRequest
3 голосов
/ 09 февраля 2011

У меня проблема с производительностью запросов в спящем режиме, которую я не могу понять. В приведенном ниже фрагменте кода мне нужно выбрать объекты по крайней мере с одним сопоставлением и фильтрованным сопоставлением. Я использую FETCH JOIN для этого, чтобы загрузить только отфильтрованные отображения. Но в этом случае у меня проблемы с производительностью запроса. Hibernate говорит предупреждение журнала:

org.hibernate.hql.ast.QueryTranslatorImpl - firstResult / maxResults, указанный в выборке коллекции; применяя в память!

Когда я опускаю FETCH JOIN и оставляю только запрос JOIN, это очень быстро. Но в результате у меня есть все сопоставления, загруженные в объект, что для меня неприемлемо. Есть ли способ повысить производительность запросов? В таблице сопоставления много строк.

HQL-запрос:

select distinct e from Entity 
   join fetch e.mappings as mapping 
where e.deleted = 0 and e.mappings is not empty 
   and e = mapping.e and mapping.approval in (:approvals)

Объекты:

@Entity
@Table(name="entity")
class Entity {

   ...

   @OneToMany(mappedBy="entity", cascade=CascadeType.REMOVE, fetch=FetchType.LAZY)
   @OrderBy("created")
   private List<Mapping> mappings = new ArrayList<Mapping>();

   ...
}

@Entity
@Table(name="mapping")
class Mapping {

public static enum MappingApproval {
    WAITING, // mapping is waiting for approval
    APPROVED, // mapping was approved
    DECLINED; // mapping was declined
}

...

    @ManyToOne(fetch=FetchType.EAGER)
    @JoinColumn(name="entity_id", nullable=false)
    private Entity entity;

    @Enumerated(EnumType.STRING)
    @Column(name="approval", length=20)
    private MappingApproval approval;

...

}

Спасибо

Ответы [ 4 ]

5 голосов
/ 24 мая 2012

Из JPA-спецификаций

Эффект применения setMaxResults или setFirstResult к запросу, включающему выборочные объединения по коллекциям, не определен.(JPA «Enterprise JavaBeans 3.0, финальная версия», интерфейс запросов Kapitel 3.6.1)

Hibernate делает правильные вещи, но выполняет часть запроса в памяти, что значительно медленнее.В моем случае разница составляет от 3-5 мс до 400-500 мс.

Мое решение состояло в том, чтобы реализовать подкачку в самом запросе.Быстро работает с JOIN FETCH.

1 голос
/ 10 февраля 2011

после увеличения памяти для JVM дела идут намного лучше. В конце концов я заканчиваю тем, что не использую FETCH в запросах.

0 голосов
/ 13 сентября 2017

Причина медленная, потому что Hibernate выполняет SQL-запрос без нумерации страниц и ограничение выполняется в памяти.

Однако, если объединение должно сканировать и извлекать записи по 100 Кб, в то время как вы заинтересованытолько в 100 результатах 99,9% работы, выполняемой Extractor, и все операции ввода-вывода, выполняемые по сети, просто напрасны.

Как я объяснил в этой статье , вы можете легко превратить JPQL-запрос, который использует как JOIN FETCH, так и пагинацию:

List<Post> posts = entityManager.createQuery(
    "select p " +
    "from Post p " +
    "left join fetch p.comments " +
    "where p.title like :title " +
    "order by p.id", Post.class)
.setParameter("title", titlePattern)
.setMaxResults(maxResults)
.getResultList();

, в SQL-запрос, ограничивающийрезультат с использованием DENSE_RANK по родительскому идентификатору:

@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"),
            }
        )
    }
)

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

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

Чтобы преобразовать набор табличных результатов обратно в граф сущностей, вам нужен ResultTransformer, который выглядит следующим образом:

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

Вот и все!

0 голосов
/ 06 июля 2017

Если вам нужен firstResult / maxResults с «fetch», вы можете разделить ваш запрос на 2 запроса:

  1. Запросите идентификаторы вашей сущности с помощью firstResult / maxResults, но без «выборки» для подстолов:

    select entity.id from entity (without fetch) where .... (with firstResult/maxResults)
    
  2. Запросите ваши сущности с помощью "fetch" на идентификаторах, возвращаемых вашим первым запросом:

    select entity from entity fetch ... where id in <previous ids>
    
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...