【问题标题】:What's the goal of the JPA Entity Graph?JPA 实体图的目标是什么?
【发布时间】:2015-10-20 14:47:01
【问题描述】:

我一直在学习JPA,发现我们可以从JPA 2.1 开始使用实体图。

但我还没有理解实体图的优点。

我知道使用实体图的优点之一是我们可以只指定我们想要在整个实体中获取的数据,但是如果我们想要一个完整的实体,还有其他理由使用实体图?

或者我们应该只在我们想要检索部分数据时才使用实体图?

如果我们使用实体图有什么其他的目的或优点,我很想知道。

【问题讨论】:

标签: java hibernate jpa entity entitygraph


【解决方案1】:

在 JPA/Hibernate 中,获取具有关联的实体一直是 性能问题。

  • 在事务中一次又一次延迟加载关联会导致 n+1 选择问题并避免此类问题 JPQL 使用 join fetch 和 Criteria api 连接。但是用 这两个也导致交叉连接问题意味着所有的交叉连接 表记录由休眠返回给应用程序。
  • 此外,在实体级别更改注释中定义的 fetch 变量也不是基于用例的好选择。
  • 因此引入了实体图来解决上述两个问题。实体图中定义的所有节点总是渴望的 无论它们在实体级别的定义如何,都可以获取。这些 图表作为提示传递给查询。
  • 通过传递图形作为提示,交叉连接问题也得到解决,注释级别指定的关联获取行为可以 也可以更改。

代码可以查看my Github repository

【讨论】:

    【解决方案2】:

    JPA Entity Graph 允许您覆盖默认提取计划。

    默认提取计划

    正如我在 this article 中解释的那样,每个实体都有一个在实体映射期间定义的默认获取计划,并指示 Hibernate 如何获取实体关联。

    默认情况下,@ManyToOne@OneToOne 关联使用 FetchTyp.EAGER 策略,从性能角度来看,这是一个糟糕的选择。因此,出于这个原因,最好将所有 @ManyToOne@OneToOne 关联设置为使用 FetchType.LAZY 策略,如下例所示:

    @Entity(name = "PostComment")
    @Table(name = "post_comment")
    public class PostComment {
    
        @Id
        private Long id;
    
        @ManyToOne(fetch = FetchType.LAZY)
        private Post post;
    
        private String review;
        
        //Getters and setters omitted for brevity
    }
    

    当使用find 方法获取PostComment 实体时:

    PostComment comment = entityManager.find(PostComment.class, 1L);
    

    Hibernate 执行以下 SQL 查询:

    SELECT pc.id AS id1_1_0_,
           pc.post_id AS post_id3_1_0_,
           pc.review AS review2_1_0_
    FROM post_comment pc
    WHERE pc.id = 1
    

    post 关联作为 Proxy 提取,该关联仅具有由上述 SQL 查询加载的 post_id 外键列设置的 id

    访问post代理的任何非id属性时:

    LOGGER.info("The comment post title is '{}'", comment.getPost().getTitle());
    

    执行辅助 SQL 查询,按需获取 Post 实体:

    SELECT p.id AS id1_0_0_,
           p.title AS title2_0_0_
    FROM post p
    WHERE p.id = 1
    
    -- The comment post title is 'High-Performance Java Persistence, part 1'
    

    覆盖默认提取计划

    如果我们想覆盖默认获取计划并在查询执行时急切地获取 post 关联,我们可以使用 JPQL 查询来指示 Hibernate 使用 FETCH JOIN 子句获取惰性关联:

    PostComment comment = entityManager.createQuery("""
        select pc
        from PostComment pc
        left join fetch pc.post
        where pc.id = :id
        """, PostComment.class)
    .setParameter("id", 1L)
    .getSingleResult();
    
    LOGGER.info("The comment post title is '{}'", comment.getPost().getTitle());
    

    然后,默认的获取计划将被覆盖,post 关联将被急切地获取:

    SELECT pc.id AS id1_1_0_,
           p.id AS id1_0_1_,
           pc.post_id AS post_id3_1_0_,
           pc.review AS review2_1_0_,
           p.title AS title2_0_1_
    FROM post_comment pc
    LEFT JOIN post p ON pc.post_id = p.id
    WHERE pc.id = 1
    

    声明性 JPA 实体图

    也可以使用 JPA 实体图覆盖默认提取计划。例如,我们可以使用以下 JPA @EntityGraph 注释定义特定的获取计划:

    @Entity(name = "PostComment")
    @Table(name = "post_comment")
    @NamedEntityGraph(
        name = "PostComment.post",
        attributeNodes = @NamedAttributeNode("post")
    )
    public class PostComment {
        //Code omitted for brevity
    }
    

    有了PostComment.post 实体图,我们现在可以加载PostComment 实体及其关联的post 实体,如下所示:

    PostComment comment = entityManager.find(
        PostComment.class, 
        1L,
        Collections.singletonMap(
            "javax.persistence.loadgraph",
            entityManager.getEntityGraph("PostComment.post")
        )
    );
    

    并且,在执行上述find 方法时,Hibernate 会生成以下 SQL SELECT 查询:

    SELECT pc.id AS id1_1_0_,
           pc.post_id AS post_id3_1_0_,
           pc.review AS review2_1_0_,
           p.id AS id1_0_1_,
           p.title AS title2_0_1_
    FROM post_comment pc
    LEFT OUTER JOIN post p ON pc.post_id = p.id
    WHERE pc.id = 1
    

    如果您使用的是 Spring,那么您可以使用 @EntityGraph 注释在 Repository 方法中引用 JPA 实体图:

    @Repository
    public interface PostCommentRepository 
            extends CrudRepository<PostComment, Long> {
    
        @EntityGraph(
            value = "PostComment.post", 
            type = EntityGraphType.LOAD
        )
        PostComment findById(Long id);
    }
    

    程序化 JPA 实体图

    如果您不喜欢注解,那么您也可以使用 JPA EntityManagercreateEntityGraph 方法以编程方式构建 JPA 实体图,如下例所示:

    EntityGraph<PostComment> postCommentGraph = entityManager
        .createEntityGraph(PostComment.class);
        
    postCommentGraph.addAttributeNodes("post");
    
    PostComment comment = entityManager.find(
        PostComment.class, 
        1L,
        Collections.singletonMap(
            "javax.persistence.loadgraph",
            postCommentGraph
        )
    );
    

    【讨论】:

      猜你喜欢
      • 2012-04-04
      • 1970-01-01
      • 2013-09-09
      • 2014-04-07
      • 2011-05-27
      • 2015-11-05
      • 1970-01-01
      • 2010-09-16
      • 1970-01-01
      相关资源
      最近更新 更多