Эффективная загрузка нескольких сумок - PullRequest
3 голосов
/ 20 апреля 2010

Я разрабатываю многоязычное приложение. По этой причине многие объекты имеют в своих полях имени и описания коллекции чего-то, что я называю LocalizedStrings вместо простых строк. Каждая LocalizedString - это пара локалей и строка, локализованная для этой локали.

Давайте рассмотрим пример сущности, скажем, книги -объекта.

public class Book{

 @OneToMany
 private List<LocalizedString> names;

 @OneToMany
 private List<LocalizedString> description;

 //and so on...
}

Когда пользователь запрашивает список книг, он запрашивает все книги, выбирает название и описание каждой книги в локали, выбранной пользователем для запуска приложения, и отображает ее обратно пользователь.

Это работает, но это серьезная проблема с производительностью. На данный момент hibernate делает один запрос для извлечения всех книг, и после этого он проходит через каждый отдельный объект и запрашивает hibernate для локализованных строк для этого конкретного объекта, что приводит к «проблеме выбора n + 1». При извлечении списка из 50 объектов в моем журнале сервера появляется около 6000 строк команд sql.

Я пытался сделать коллекции нетерпеливыми, но это привело меня к вопросу «не могу одновременно получить несколько сумок».

Затем я попытался установить стратегию выборки для коллекций для подвыбора, надеясь, что он сделает один запрос для всех книг, а после этого сделает один запрос, который извлекает все LocalizedStrings для всех книг. Подвыборки в этом случае не сработали так, как я бы надеялся, и в основном это было точно так же, как в моем первом случае.

У меня заканчиваются идеи о том, как это оптимизировать.

Короче говоря, какие есть альтернативы стратегии извлечения, когда вы извлекаете коллекцию, и каждый элемент в этой коллекции имеет одну или несколько коллекций в себе, которые должны извлекаться одновременно.

Ответы [ 2 ]

3 голосов
/ 20 апреля 2010

Вы сказали

Я попытался установить выборочную стратегию выборки для коллекций, надеясь, что она сделает один запрос для всех книг

Можно, но вам нужно получить доступ к какому-либо свойству, чтобы бросить подвыбор

@Entity
public class Book{

    private List<LocalizedString> nameList = new ArrayList<LocalizedString>();

    @OneToMany(cascade=javax.persistence.CascadeType.ALL)
    @org.hibernate.annotations.Fetch(org.hibernate.annotations.FetchMode.SUBSELECT)
    public List<LocalizedString> getNameList() {
        return this.nameList;
    }

    private List<LocalizedString> descriptionList = new ArrayList<LocalizedString>();

    @OneToMany(cascade=javax.persistence.CascadeType.ALL)
    @org.hibernate.annotations.Fetch(org.hibernate.annotations.FetchMode.SUBSELECT)
    private List<LocalizedString> getDescriptionList() {
        return this.descriptionList;
    }



}

Действуйте следующим образом

public class BookRepository implements Repository {

    public List<Book> getAll(BookFetchingStrategy fetchingStrategy) {
        switch(fetchingStrategy) {
            case BOOK_WITH_NAMES_AND_DESCRIPTIONS:
                List<Book> bookList = session.createQuery("from Book").list();

                // Notice empty statement in order to start each subselect
                for (Book book : bookList) {
                    for (Name address: book.getNameList());
                    for (Description description: book.getDescriptionList());
                }

            return bookList;
        }
    }

    public static enum BookFetchingStrategy {
        BOOK_WITH_NAMES_AND_DESCRIPTIONS;
    }

}

Я сделал следующее, чтобы заполнить базу данных

SessionFactory sessionFactory = configuration.buildSessionFactory();

Session session = sessionFactory.openSession();
session.beginTransaction();

// Ten books
for (int i = 0; i < 10; i++) {
    Book book = new Book();
    book.setName(RandomStringUtils.random(13, true, false));

    // For each book, Ten names and descriptions
    for (int j = 0; j < 10; j++) {
        Name name = new Name();
        name.setSomething(RandomStringUtils.random(13, true, false));

        Description description = new Description();
        description.setSomething(RandomStringUtils.random(13, true, false));

        book.getNameList().add(name);
        book.getDescriptionList().add(description);
    }

    session.save(book);
}

session.getTransaction().commit();
session.close();

и для извлечения

session = sessionFactory.openSession();
session.beginTransaction();

List<Book> bookList = session.createQuery("from Book").list();

for (Book book : bookList) {
    for (Name address: book.getNameList());
    for (Description description: book.getDescriptionList());
}

session.getTransaction().commit();
session.close();

Понятно

Спящий режим:

select
    book0_.id as id0_,
    book0_.name as name0_ 
from
    BOOK book0_

Hibernate: возвращает 100 строк (как и ожидалось)

select
    namelist0_.BOOK_ID as BOOK3_1_,
    namelist0_.id as id1_,
    namelist0_.id as id1_0_,
    namelist0_.something as something1_0_ 
from
    NAME namelist0_ 
where
    namelist0_.BOOK_ID in (
        select
            book0_.id 
        from
            BOOK book0_
    )

Hibernate: возвращает 100 строк (как и ожидалось)

select
    descriptio0_.BOOK_ID as BOOK3_1_,
    descriptio0_.id as id1_,
    descriptio0_.id as id2_0_,
    descriptio0_.something as something2_0_ 
from
    DESCRIPTION descriptio0_ 
where
    descriptio0_.BOOK_ID in (
        select
            book0_.id 
        from
            BOOK book0_
    )

Три выбранных утверждения. Нет проблемы выбора "n + 1". Помните, что я использую стратегию доступа к свойству вместо поля. Имейте это в виду.

1 голос
/ 20 апреля 2010

Вы можете установить batch-size для ваших сумок, когда одна унифицированная коллекция инициализируется, Hibernate инициализирует некоторые другие коллекции одним запросом

Больше в Hibernate Doc

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