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

Можно ли использовать Hibernate в приложениях, чувствительных к производительности?

Я вижу проблемы с производительностью при получении нескольких экземпляров объектов, которые имеют много связей с другими объектами. Я использую Spring и Hibernate JPA реализацию с MySQL. Проблема заключается в том, что при выполнении запроса JPA Hibernate не присоединяется автоматически к другим таблицам. Это приводит к n * r + 1 SQL-запросам, где n - количество извлекаемых объектов, а r - количество взаимосвязей.

Например, человек живет по адресу, имеет много хобби и побывал во многих странах:

@Entity
public class Person {
    @Id public Integer personId;    
    public String name;    
    @ManyToOne public Address address;    
    @ManyToMany public Set<Hobby> hobbies;    
    @ManyToMany public Set<Country> countriesVisited;
}

Когда я выполняю запрос JPA, чтобы получить всех людей с именем Боб, и в базе данных есть 100 Бобов:

SELECT p FROM Person p WHERE p.name='Bob'

Hibernate переводит это в 301 запрос SQL:

SELECT ... FROM Person WHERE name='Bob'
SELECT ... FROM Address WHERE personId=1
SELECT ... FROM Address WHERE personId=2
...
SELECT ... FROM Hobby WHERE personId=1
SELECT ... FROM Hobby WHERE personId=2
...
SELECT ... FROM Country WHERE personId=1
SELECT ... FROM Country WHERE personId=2
...

Согласно Hibernate FAQ (здесь и здесь), решение состоит в том, чтобы указать LEFT JOIN или LEFT OUTER JOIN (для многих ко многим) в запросе. Теперь мой запрос выглядит так:

SELECT p, a, h, c FROM Person p
LEFT JOIN p.address a LEFT OUTER JOIN p.hobbies h LEFT OUTER JOIN p.countriesVisited c
WHERE p.name = 'Bob'

Это работает, но, как представляется, возникает ошибка, если имеется более одного LEFT OUTER JOIN, и в этом случае Hibernate неправильно ищет несуществующий столбец:

could not read column value from result set: personId69_2_; Column 'personId69_2_' not found.

Поведение ошибки может быть устранено с помощью ошибки Hibernate Core HHH-3636. К сожалению, исправление не является частью какого-либо выпущенного Hibernate JAR. Я запустил свое приложение против сборки моментального снимка, но поведение ошибки все еще присутствует. Я также собрал свой собственный Hibernate Core JAR из последнего кода в хранилище, и поведение ошибки все еще присутствует. Так что, возможно, HHH-3636 не решает эту проблему.

Это ограничение производительности Hibernate очень расстраивает. Если я запрашиваю 1000 объектов, то в базу данных поступает 1000 * r + 1 SQL-запросов. В моем случае у меня 8 отношений, поэтому я получаю 8001 SQL-запрос, что приводит к ужасной производительности. Официальное решение Hibernate для этого состоит в том, чтобы оставить все отношения. Но это невозможно с более чем одним отношением "многие ко многим" из-за ошибки в поведении. Так что я застрял с левыми соединениями для отношений многие-к-одному и n * r + 1 запросов из-за отношений многие-ко-многим. Я планирую представить проблему LEFT OUTER JOIN как ошибку Hibernate, но в то же время моему клиенту нужно приложение с разумной производительностью. В настоящее время я использую комбинацию пакетной выборки (BatchSize), ehcache и пользовательского кэширования в памяти, но производительность все еще довольно низкая (улучшено получение 5000 объектов за 30–8 секунд). Суть в том, что слишком много SQL-запросов попадают в базу данных.

Итак, мои вопросы, возможно ли использовать Hibernate в чувствительных к производительности приложениях, где таблицы имеют множественные отношения друг с другом? Мне бы очень хотелось услышать, насколько успешно Hibernate использует производительность адресов. Должен ли я писать SQL вручную (что несколько противоречит цели использования Hibernate)? Нужно ли отменять нормализацию схемы базы данных, чтобы уменьшить количество соединяемых таблиц? Разве я не должен использовать Hibernate, если мне нужна высокая производительность запросов? Есть ли что-то быстрее?

4b9b3361

Ответ 1

Посмотрите мой ответ на свой другой вопрос, если вы прочитали все часто задаваемые вопросы, на которые вы ссылались:

Следуйте руководству по лучшим практикам! Убедитесь, что все и сопоставления указывают lazy = "true" в Hibernate2 (это новое значение по умолчанию в Hibernate3). Используйте HQL LEFT JOIN FETCH, чтобы указать, какие ассоциации вам нужно получить в начальном SQL SELECT.

Второй способ избежать проблемы выбора n + 1 - использовать fetch = "subselect" в Hibernate3.

Если вы все еще не уверены, обратитесь к документации по Hibernate и Hibernate в действии.

Ознакомьтесь с советами по повышению производительности. Если вы не будете осторожны с объединениями, у вас возникнут проблемы с декартовым продуктом.

Ответ 2

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

В вашем appContext.xml:

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    ...    
    <property name="hibernateProperties">
        <props>        
            ...
            <prop key="hibernate.default_batch_fetch_size">32</prop>
        </props>
    </property>
</bean>

Так что вместо:

SELECT ... FROM Hobby WHERE personId=1
SELECT ... FROM Hobby WHERE personId=2

Вы получите:

SELECT ... FROM Hobby WHERE personId in (1,2,...,32);
SELECT ... FROM Hobby WHERE personId in (33,34,...,64);

Ответ 4

Если вам нужна функция Hibernate, и эта функция глючит, у вас есть два варианта: a) Отправьте запрос об ошибке и используйте обходной путь (низкая производительность или рукописный sql), пока ошибка не будет исправлена, что займет некоторое время б) Отправить запрос об ошибке вместе с исправлением и тестами. (конечно, вы можете просто использовать исправление и пропустить запрос на исправление ошибок и тестовую часть).