【问题标题】:Hibernate unidirectional @OneToOne using inner join使用内部连接的休眠单向@OneToOne
【发布时间】:2017-09-25 15:45:32
【问题描述】:

我正在使用:

  • 春季启动
  • Spring 数据 JPA
  • Spring 数据休息
  • 休眠
  • 嵌入式 H2 数据库

我正在尝试定义 2 个类,FirstSecond,这样就存在从 FirstSecond 的一对一映射。由于我正在尝试通过 Spring Data Rest 托管查询,它将数据转换为 JSON,我相信执行 EAGER 获取是最有意义的。

所以,我有以下内容:

@Entity
public class First {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "FIRST_ID")
    private long id;

    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name = "SECOND_ID")
    private Second second;

    // more/getters/settings
}

@Entity
public class Second {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "SECOND_ID")
    private long id;

    // more/getters/settings
}

当我搜索 First 类型的数据时,我得到一个 First 类型的 SELECT 查询,然后是每个 Second 的单独 SELECT 查询,其中 SECOND_ID 与 First 中引用的外键匹配。

在此数据上执行 INNER JOIN 以在单个查询中完成所有操作是最有意义的。如:

SELECT * FROM FIRST
INNER JOIN SECOND ON FIRST.SECOND_ID

我可以直接在数据库上运行这个查询并得到一个连接两个表的表。

我怎样才能让 JPA/Hibernate 做到这一点?这似乎是一个常见的操作,我觉得我错过了一些明显的东西。

编辑:请注意,我使用的是通过 Spring Data Rest 自动生成的 REST 端点运行查询。我已经定义了一个 Spring Data 存储库:

@RepositoryRestResource(path = "first")
public interface FirstRepository extends CrudRepository<First, Long> {
}

然后,通过访问http://host/first,我使 Spring 运行 findAll 操作,该操作有效,但再次触发大量 SELECT 查询。

【问题讨论】:

  • 首先非常感谢您发布这个问题。我已经搜索了几个小时来弄清楚如何使用 Spring Data REST JPA oneToOne 单向来创建满足我需求的确切用例。
  • 顺便说一句,对于任何对多个@OneToOne 用例感兴趣的人,您可以在上面的示例中添加第三个,注意,如果您还定义了一个 CRUD 存储库无论是第二个还是第三个,它都不会按预期工作。出于某种原因,您不得为任何一个方向声明存储库(第二个或随后的第三个或第四个广告。infitintum。)。我不知道为什么,但是如果你排除说第三个的 JSON,它就会失败:{ "firstName": "string", "second": { "secondName": "string" } } 所以请确保你是否想要省略定向 one2ones。

标签: hibernate jpa spring-boot spring-data-jpa spring-data-rest


【解决方案1】:

试试这个变种:

@RepositoryRestResource(path = "first")
public interface FirstRepository extends CrudRepository<First, Long> {

    @Override
    @EntityGraph(attributePaths = {"second"})
    Iterable<First> findAll();
}

【讨论】:

  • 谢谢!这正是我一直在寻找的。我想我不太明白EntityGraph 做了什么。
  • 更好的是,我想我可以定义 @NamedEntityGraph 并指定 includeAllAttributes = true 并避免引用单个属性。
  • 似乎只能指定一级子图。见:stackoverflow.com/a/39020912/1612093
  • 我真正想知道的是,如果我 NOT 使用 POS 添加第二个(即它为空)首先,我如何防止它被显示在 findAll() 中;我想我可以编写自己的查询...感谢 ALL 的这篇文章。
【解决方案2】:

首先,只有在使用EntityManager#find() 加载实体时才会尊重关联的获取提示。

如果您要获取一个单独的第一个实例 (http://host/first/1),您应该会看到在 1 个使用 LEFT OUTER JOIN 的查询中检索到的实例及其关联(将关系标记为可选 = false)将导致 INNER JOIN)因为 Spring Data 将委托给 EntityManager#find() 方法。

Spring Data 提供了一个方便的findAll() 方法,当您选择获取所有实例时,它会简单地生成一个查询"Select f from First f",并在您点击http://host/first 时调用它。所以这显然不会执行 JOIN FETCH。

如果您要创建查询:

@Query("select f from First f join fetch f.second")
public List<First> findAllEager();

然后点击http://host/first/search/findAllEager 然后这应该会在一次选择中加载所有内容。

由于这显然不是很方便,您实际上可以在存储库界面中重新定义 findAll() 方法,如下所示:

@Query("select f from First f join fetch f.second")
public Iterable<First> findAll();

附加信息:当使用 EntityManager#find() 加载实体时,@OneToOne 关联在 Hibernate 中总是急切的,除非标记为非可选和惰性。

【讨论】:

  • 啊哈! HQL 中的“join fetch”是我想要弄清楚的。这确实有效,但我认为 Cepr0 的解决方案对于我的用例来说是一个更好的解决方案,它完全避免了 @Query
  • 好吧,如果你能保证 EntityGraph 会进行连接提取,那么我猜是这样。但你不能总是确定这一点......
  • 不是这样吗?它似乎正在为我做连接,但我似乎无法找到任何文档。
  • 可能没有具体说明应该如何实现。我猜提供者可以进行加入或第二次选择,但仍然符合规范。
  • 其实我觉得这毕竟是最好的办法。 EntityGraph 似乎只允许 1 级子图。
猜你喜欢
  • 1970-01-01
  • 2012-10-14
  • 2011-02-25
  • 2012-09-06
  • 1970-01-01
  • 2011-02-13
  • 1970-01-01
  • 1970-01-01
  • 2012-08-11
相关资源
最近更新 更多