我在检索与其他对象有许多关系的对象的多个实例时遇到性能问题。我正在将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查询以获取所有名为Bob的个人时,数据库中有100个Bobs:
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和自定义内存缓存,但性能仍然很差(将30个秒内的5000个对象检索到8秒的性能得到了改善)。最重要的是,有太多的SQL查询正在访问数据库。
因此,我的问题是,可以在表之间具有多个关系的对性能敏感的应用程序中使用Hibernate吗?我很想听听Hibernate如何成功使用地址性能。我应该手写SQL(这在某种程度上违背了使用Hibernate的目的)吗?我应该对数据库架构进行非规范化以减少联接表的数量吗?如果我需要快速的查询性能,是否应该不使用Hibernate?有更快的东西吗?
如果您阅读了链接到的所有常见问题解答,请参阅我对其他问题的回答:
遵循最佳做法指南!确保在Hibernate2中所有映射和映射都指定lazy =“ true”(这是Hibernate3中的新默认设置)。使用HQL LEFT JOIN FETCH指定在初始SQL SELECT中需要检索的关联。 避免n + 1选择问题的第二种方法是在Hibernate3中使用 fetch =“ subselect” 。 如果仍然不确定,请参考Hibernate文档和Hibernate in Action。
遵循最佳做法指南!确保在Hibernate2中所有映射和映射都指定lazy =“ true”(这是Hibernate3中的新默认设置)。使用HQL LEFT JOIN FETCH指定在初始SQL SELECT中需要检索的关联。
避免n + 1选择问题的第二种方法是在Hibernate3中使用 fetch =“ subselect” 。
如果仍然不确定,请参考Hibernate文档和Hibernate in Action。
请参阅有关提高性能的提示。如果对联接不小心,则会导致笛卡尔积问题。