子选择提取导致看似无关的代码行之间的意外交互

时间:2014-10-30 22:40:25

标签: java hibernate

我有一个使用Hibernate的Tomcat Web应用程序。在对象模型的一个非常简化的版本中,我有三个类:EmployeeFooBarEmployeeFooBar都有一对多的关系。 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()来解决此特定实例中的问题,但这似乎是一个糟糕的解决方案,因为:

  1. 这是黑客和非显而易见的
  2. 我们可能会再次遇到另一个类或字段的问题,并且必须再次进行诊断
  3. 对于正确处理这个问题的方法,我真的很难过。还有其他人遇到过这个并提出一个好的解决方案吗?

    编辑:这里的每个请求是(我的映射的程式化版本):

    @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
    }
    

1 个答案:

答案 0 :(得分:0)

  1. 鉴于您的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
    

    }

  2. 通过映射字段​​获取和遍历关系的更好的替代方法是直接JPA / Hibirante查询,以获得进一步处理所需的内容。这就是为什么hibernate不必努力构建对象树,而是通过单个查询得到你想要的东西。例如,使用伪代码:

    列出resultSet = session.createQuery(“从b中选择b.employee.name,b.employee.phone,其中b.client.name =?)。setString(0,”blah“)。list(); < / p>

  3. 我还建议启用第二级和查询缓存。

  4. 希望这会有所帮助。

    此致

    Slava Imeshev