JPA Hibernate n + 1问题(Lazy& Eager Diff)

时间:2017-04-20 07:53:59

标签: hibernate spring-data-jpa

我正在尝试理解n + 1问题,从而找到正确的解决方法。

我有两个实体: 公司

@Entity
@Table(name="company")
public class Company implements Serializable {

    private static final long serialVersionUID = 1L;


    @Id
    @GeneratedValue
    private int id;

    @Column(name="cmp_id")
    private int cmpId;

    @Column(name="company_name")
    private String companyName;

    @OneToMany(fetch=FetchType.LAZY)
    @JoinColumn(name="cmp_id",referencedColumnName="cmp_id")
    private Set<Employee> employee;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getCmpId() {
        return cmpId;
    }

    public void setCmpId(int cmpId) {
        this.cmpId = cmpId;
    }

    public String getCompanyName() {
        return companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    public Set<Employee> getEmployee() {
        return employee;
    }

    public void setEmployee(Set<Employee> employee) {
        this.employee = employee;
    }



}

员工

@Entity
@Table(name="employee")
public class Employee implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue
    private int id;

    @Column(name="emp_id")
    private int empId;

    @Column(name="emp_name")
    private String empName;

    /*@ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="cmp_id", referencedColumnName="cmp_id")
    @JsonIgnore
    private Company company;*/

    @Column(name="cmp_id")
    private int cmpId;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getEmpId() {
        return empId;
    }

    public void setEmpId(int empId) {
        this.empId = empId;
    }

    public String getEmpName() {
        return empName;
    }

    public void setEmpName(String empName) {
        this.empName = empName;
    }

}

每家公司都有很多员工。如此简单的UNI-DIRECTIONAL一对多关系。 现在当我运行查询时(&#34;从公司a和#34中选择一个),我面临n + 1选择(当我想要获得员工时)

但为了更清楚地理解概念,当我将其更改为EAGER时,所有相同的n + 1查询最初都在运行(即使我没有获取员工)。这是正确的行为吗?我的意思是不应该触发连接查询。另外,如何使用EAGER更改代码以仅产生1个查询。?

3 个答案:

答案 0 :(得分:4)

&#34;问题&#34;这不是一个真正的问题,而是关于ORM如何运作的问题。如果您访问这样的关联,Hibernate会创建所谓的nested queries

你是对的,在这两种情况下执行相同的查询,将映射的FetchTypeLAZY切换到EAGER只是安排执行额外的(n + 1) )查询。

假设您有许多公司,所有公司都有员工,在这两种情况下都会执行这样的查询(至少一次):

select ... from company company0_

select ... from employee employee0_ where employee0_.cmp_id=?

第一个被执行以获得所有公司,第二个被执行到每个公司。

例如:拥有许多员工的3家公司(N)将执行第一次选择,三次嵌套选择= 3 + 1 = 4次查询。

EAGERLAZY之间的区别仅在于时间点,您通常无法避免数据库访问,因为您仍需要数据。使用LAZY,只是推迟了附加查询,直到您迭代员工集合。但请记住,它只是一个提示,并非每个数据库驱动程序都支持延迟加载。

如果你真的知道你总是需要数据,你可以写一个FETCH JOIN查询并一次性接收所有需要的数据:

Select c from Company c JOIN FETCH c.employee e

这将执行如下查询:

select ... from company company0_ inner join employee employee1_ on company0_.cmp_id=employee1_.cmp_id

这将避免第二次数据库访问。要在测试中验证,the datasource proxy project可能适合您。

答案 1 :(得分:0)

要更深入了解n+1已查看this已经得到很好回答的问题。

要解决您的问题,您可以通过在Company jpa存储库中编写此查询来热切地获取所有子实体。

@Query("Select c from Company c join fetch c.employee e where c.id = :cmpId")
public Company fetchCompanyAndEmployeesEager(@Param("cmpId") long id);

PS:我没有测试过该查询但它应该有效。

答案 2 :(得分:0)

面对N + 1问题时,有几种解决方案可以解决此问题。 本机解决方案是使用@BatchSize批注。

根据休眠文档(http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html_single/#performance-fetching-batch):

  

20.1.5。使用批量获取使用批量获取,如果访问了一个代理,则Hibernate可以加载多个未初始化的代理。批量提取   是延迟选择获取策略的优化。那里有两个   配置批量读取的方式:在类级别和   收集级别。

     

批量获取类/实体更容易理解。考虑   以下示例:在运行时,您加载了25个Cat实例   一个会话,并且每个Cat都引用其所有者Person。的   Person类通过代理lazy =“ true”映射。如果现在迭代   通过所有的猫并在每只猫上调用getOwner(),Hibernate将通过   默认情况下,执行25条SELECT语句以检索代理所有者。   您可以通过在映射中指定一个批处理大小来调整此行为   人数:

     

<class name="Person" batch-size="10">...</class>

     

具有此批处理大小   指定后,Hibernate现在将在需要时按需执行查询   如上所述,访问未初始化的代理,但不同之处在于   而不是查询所访问的确切代理实体,   会立即查询更多人的所有者,因此,在访问其他人时   此人的所有者,可能已通过此批次获取进行了初始化   仅执行少数查询(少于25个)。

因此,您可以将@BatchSize(size=10)添加到您的关系中(可以调整大小):

@OneToMany(fetch=FetchType.LAZY)
@JoinColumn(name="cmp_id",referencedColumnName="cmp_id")
@BatchSize(size = 10)
private Set<Employee> employee;

当您使用@BatchSize批注时,它会在获取子项(如:)时添加一个where查询:

select [columns] from child_table where parent_id in (?, ?, ? ...)

另一种解决方案是在查询中使用join fetch

entityManager.createQuery("select distinct c from Company join fetch c.emplee").getResultList();

使用联接获取时,它将生成一个内部联接查询。我们添加了独特的关键字以避免由于内部联接而造成重复。

相关问题