我有一个使用Hibernate的Tomcat Web应用程序。在对象模型的一个非常简化的版本中,我有三个类:Employee
,Foo
和Bar
。 Employee
与Foo
和Bar
都有一对多的关系。 Foo
与系统中的许多其他未命名对象具有多对一关系。
在处理特定页面请求时,我的应用程序将(都在一个Hibernate会话中):
Employee emp = session.get(Employee.class, id);
List<Foo> foos = session.createCriteria(Foo.class).add(eq("emp", emp)).list();
for (Bar bar : emp.getBars()) bar.doStuff();
实际上这三行距离很远,实际上是在单独的源文件中。特别是,第1行在每个请求上运行(它获取登录用户),第2行获取有问题页面感兴趣的数据,第3行获取侧栏中每个页面上显示的一些数据。 / p>
另请注意,bar.doStuff()
实际上在对象图中距离Bar
几个级别(每个员工实际上分配给某些具有某些任务的项目的客户)。
在第2行中,一切都运行顺畅,当我们迭代emp.getBars()
时生成的SQL很简单,只有select stuff where Bar.empID = ?
。 Bar
的子元素的进一步子选择确实有越来越多的子查询,但客观上没有花费大量的时间,我认为MySQL足够聪明,可以优化它们。
然而,当在第1行和第3行之间执行第2行时,第3行需要很长时间。在查看生成的SQL之后,我确信这与子选择行为以及Foo
具有如此多* ToOne关系的事实有关。
第2行的查询有很多外部和内部连接,因为Hibernate实际上不支持对* ToOne关系的子选择获取。运行第2行查询时,它必须再次检索emp
。当然,Hibernate 通知它已经检索了emp
,因此实际上并没有构造一个新的Employee
对象,而是将已经创建的实例分配给每个内部的字段。 Foo
。
问题是,现在最新的“检索”emp
的查询是一个很难看的查询,有很多连接。因此,当我们尝试获取所有Bar
时,生成的SQL现在采用以下形式:select stuff from Bar where Bar.empID in (select Foo.empID from Foo join many tables)
。
当我们从Bar
深入到对象图中时,这个问题当然会重复多次。最终结果是页面加载几乎需要30秒。
我可以通过强制初始化emp.getBars()
来解决此特定实例中的问题,但这似乎是一个糟糕的解决方案,因为:
对于正确处理这个问题的方法,我真的很难过。还有其他人遇到过这个并提出一个好的解决方案吗?
编辑:这里的每个请求是(我的映射的程式化版本):
@Entity
public class Employee
{
@OneToMany(mappedBy = "emp")
@Fetch(FetchMode.SUBSELECT)
private Set<Bar> bars = new HashSet<>();
}
@Entity
public class Foo
{
@ManyToOne(optional = false)
@JoinColumn(name = "empID")
private Employee emp;
@ManyToOne
@JoinColumn(name = "class1_ID")
private Class1 class1;
// repeat for Class2, 3, 4, etc.
// some are nullable, others not
}
@Entity
public class Bar // Bar represents an employee being assigned to a client
{
@ManyToOne(optional = false)
@JoinColumn(name = "empID")
private Employee emp;
@ManyToOne(optional = false)
@JoinColumn(name = "clientID")
private Client client;
// Client itself has-many Projects which have-many Tasks
// which are all themselves mapped entities and the objects we ultimately want
}
答案 0 :(得分:0)
鉴于您的Foo和Bar有很多多对一的关系,我建议您尝试使用延迟抓取,请注意使用fetch = LAZY:
@Entity 公共课Foo { @ManyToOne(fetch = LAZY,optional = false) @JoinColumn(name =“empID”) 私人雇员emp;
@ManyToOne(fetch=LAZY)
@JoinColumn(name = "class1_ID")
private Class1 class1;
// repeat for Class2, 3, 4, etc.
// some are nullable, others not
}
通过映射字段获取和遍历关系的更好的替代方法是直接JPA / Hibirante查询,以获得进一步处理所需的内容。这就是为什么hibernate不必努力构建对象树,而是通过单个查询得到你想要的东西。例如,使用伪代码:
列出resultSet = session.createQuery(“从b中选择b.employee.name,b.employee.phone,其中b.client.name =?)。setString(0,”blah“)。list(); < / p>
我还建议启用第二级和查询缓存。
希望这会有所帮助。
此致
Slava Imeshev