简体   繁体   English

了解 spring-data 如何处理 @EntityGraph

[英]Understanding how spring-data handles @EntityGraph

(I made a SSCCE for this question.) (我为这个问题做了一个SSCCE 。)

I have 2 simple entities: Employee and Company .我有 2 个简单的实体: EmployeeCompany Employee has a @ManyToOne relationship with Company with default fetch strategy (eager). Employee与具有默认获取策略(eager)的Company之间存在@ManyToOne关系。

I want to be able to load the Employee without the Company without changing the fetch strategy defined in the Employee because I need to do that for only one use case.我希望能够在不更改Employee中定义的获取策略的情况下加载没有CompanyEmployee ,因为我只需要为一个用例执行此操作。

JPA's entity graph seems to be intended for this purpose. JPA 的实体图似乎就是为此目的而设计的。

So I defined a @NamedEntityGraph on the class Employee :所以我在 class Employee上定义了一个@NamedEntityGraph

@Entity
@NamedEntityGraph(name = "employeeOnly")
public class Employee {

  @Id
  private Integer id;
  private String name;
  private String surname;
  @ManyToOne
  private Company company;

  //Getters & Setters

And a EmployeeRepository like this:还有一个像这样的EmployeeRepository

public interface EmployeeRepository extends CrudRepository<Employee, Integer> {

  @EntityGraph(value = "employeeOnly", type = EntityGraph.EntityGraphType.FETCH)
  List<Employee> findByCompanyId(Integer companyId);

}

Despite the use of @EntityGraph , I can see in the logs that the Company is still loaded by hibernate:尽管使用了@EntityGraph ,但我可以在日志中看到Company仍然由 hibernate 加载:

2016-11-07 23:16:08.738 DEBUG 1029 --- [nio-8080-exec-2] org.hibernate.SQL                        : select employee0_.id as id1_1_, employee0_.company_id as company_4_1_, employee0_.name as name2_1_, employee0_.surname as surname3_1_ from employee employee0_ left outer join company company1_ on employee0_.company_id=company1_.id where company1_.id=?
2016-11-07 23:16:08.744 DEBUG 1029 --- [nio-8080-exec-2] org.hibernate.SQL                        : select company0_.id as id1_0_0_, company0_.name as name2_0_0_ from company company0_ where company0_.id=?

Why?为什么? How to avoid that?如何避免这种情况?

Currently, Hibernate does not support handling non-lazy attributes as lazy, even with entity graphs. 目前,即使使用实体图,Hibernate也不支持将非惰性属性视为惰性。 There is an open issue for this: HHH-8776 . 有一个未解决的问题: HHH-8776

So, the only solution for the time being is to make the association lazy. 因此,目前唯一的解决方案是使关联变得懒惰。

Modified Answer 修改后的答案

per- specification , the fetch type for @ManyToOne is EAGER by default. 根据规范 ,默认情况下@ManyToOne的获取类型为EAGER But even through we set: 但即便通过我们设定:

@ManyToOne(fetch = FetchType.LAZY)
private Company company;

You will get the same result. 你会得到相同的结果。 The problem because the way spring-data-jpa create HQL/JPQL for you. 问题是因为s​​pring-data-jpa为你创建HQL / JPQL的方式。 So adding @ManyToOne(fetch = FetchType.LAZY) 所以添加@ManyToOne(fetch = FetchType.LAZY) won't work 不行 is not enough. 是不足够的。 To solve this, use @ManyToOne(fetch = FetchType.LAZY) and @Query annotation in your repository: 要解决此问题,请在存储库中使用@ManyToOne(fetch = FetchType.LAZY) @Query注释:

Employee.java : Employee.java

@ManyToOne(fetch = FetchType.LAZY)
private Company company;

EmployeeRepository.java EmployeeRepository.java

@Query("from Employee e where e.company.id = :companyId")
List<Employee> findByCompanyIdUsingQuery(@Param("companyId") Integer companyId);

In the test, this is SQL that generated by your loadByCompanyId() (which is generate left outer join): 在测试中,这是由loadByCompanyId()生成的SQL(生成左外连接):

select employee0_.id as id1_1_, employee0_.company_id as company_4_1_, employee0_.name as name2_1_, employee0_.surname as surname3_1_ from employee employee0_ left outer join company company1_ on employee0_.company_id=company1_.id where company1_.id=?

And this is SQL generated by method that use @Query annotation: 这是由使用@Query注释的方法生成的SQL:

select employee0_.id as id1_1_, employee0_.company_id as company_4_1_, employee0_.name as name2_1_, employee0_.surname as surname3_1_ from employee employee0_ where employee0_.company_id=?

You could check the latest code in my repository . 您可以检查我的存储库中的最新代码。

HTH. HTH。

Seems a bug in Hibernate. 似乎是Hibernate中的一个错误。

@Dragan Bozanovic you are right. @Dragan Bozanovic你是对的。 I only see one workaround in this case. 在这种情况下,我只看到一个解决方法。

Set fetch = lazy 设置fetch = lazy

@Entity
@NamedEntityGraph(name = "Employee.withCompany" , attributeNodes = @NamedAttributeNode("company"))
public class Employee {

  @Id
  private Integer id;
  private String name;
  private String surname;
  @ManyToOne(fetch = FetchType.LAZY)
  private Company company;

Introduce new method for loading company eagerly 为热切加载公司引入新方法

public interface EmployeeRepository extends CrudRepository<Employee, Integer> {


  List<Employee> findByCompanyId(Integer companyId);

  @Query("select e from Employee e left join e.company c where c.id = :companyId")
  @EntityGraph(value = "Employee.withCompany", type = EntityGraph.EntityGraphType.FETCH)
  List<Employee> findByCompanyIdFetchingCompany(@Param("companyId") Integer companyId);

}

And use following two interchangeably where required 如果需要,可以互换使用以下两个

  @RequestMapping(value = "/by-company/{id}")
  public void loadByCompanyId(@PathVariable Integer id) {
    employeeService.loadByCompanyId(id);
  }

  @RequestMapping(value = "/by-company/eager-company/{id}")
  public void loadByCompanyIdFetchingCompany(@PathVariable Integer id) {
    employeeService.loadByCompanyIdFetchingCompany(id);
  }

First one (for lazy loading) http://localhost:8080/employees/by-company/42 第一个(用于延迟加载) http:// localhost:8080 / employees / by-company / 42

select employee0_.id as id1_1_, employee0_.company_id as company_4_1_, employee0_.name as name2_1_, employee0_.surname as surname3_1_ from employee employee0_ left outer join company company1_ on employee0_.company_id=company1_.id where company1_.id=?

Second (eager loading) http://localhost:8080/employees/by-company/eager-company/42 第二(急切加载) http:// localhost:8080 / employees / by-company / eager-company / 42

select employee0_.id as id1_1_0_, company1_.id as id1_0_1_, employee0_.company_id as company_4_1_0_, employee0_.name as name2_1_0_, employee0_.surname as surname3_1_0_, company1_.name as name2_0_1_ from employee employee0_ left outer join company company1_ on employee0_.company_id=company1_.id where company1_.id=?

I was under impression you have to specify fields within your Graph definition. 我的印象是您必须在图表定义中指定字段。

https://docs.oracle.com/javaee/7/tutorial/persistence-entitygraphs001.htm (43.1.2.1 Fetch Graphs) https://docs.oracle.com/javaee/7/tutorial/persistence-entitygraphs001.htm(43.1.2.1获取图表)

A fetch graph consists of only the fields explicitly specified in the EntityGraph instance, and ignores the default entity graph settings. 获取图仅包含EntityGraph实例中显式指定的字段,并忽略默认实体图设置。

Maybe not so cool solution is using a native query. 也许不是那么酷的解决方案是使用本机查询。

 @Query(value = "select * from employee where company_id= ?1", nativeQuery = true)
  List<Employee> findByCompanyId(Integer companyId);

I tested it and it was giving expected results, without being forced to set fetch = FetchType.LAZY 我测试了它并且它给出了预期的结果,而没有被强制设置fetch = FetchType.LAZY

though the association is lazy by specifying (fetch=FetchType.Lazy) the company is exposed with a separate query call because you are exposing the Entity Employee which has property company.尽管通过指定 (fetch=FetchType.Lazy) 关联是惰性的,但公司会通过单独的查询调用公开,因为您正在公开具有财产公司的实体雇员。 If you don't want the company to be exposed then either use @JsonIgnore, which is not very much recommended.如果您不希望公司被公开,那么可以使用@JsonIgnore,这不是很推荐。 And here comes one of the reasons why we make used of DTO so that we can expose only the required fields.这就是我们使用 DTO 的原因之一,这样我们就可以只公开必需的字段。 You can create an EmployeeDTO with employee specific fields keep the association lazy which will ensure that separate query is not executed to fetch company details and map the entity to DTO either explicitly or you can make use of MapSturct Api您可以创建一个具有员工特定字段的 EmployeeDTO 保持关联惰性,这将确保不执行单独的查询以获取公司详细信息和 map 显式到 DTO 的实体,或者您可以使用 MapSturct Api

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM