懒惰的一对一 Spring JPA 和构建“动态”JSON

Posted

技术标签:

【中文标题】懒惰的一对一 Spring JPA 和构建“动态”JSON【英文标题】:Lazy One-To-One Spring JPA and building "dynamic" JSON 【发布时间】:2016-07-03 20:38:33 【问题描述】:

我正在使用 Spring Boot 开发一个相对较大的项目,总的来说我对它很满意,但我遇到了一些我认为不应该成为问题的问题。

    首先,一对一的关系。令人沮丧的是它不能正常工作(至少在我看来)。

    例如,我有两个实体,UserUserProfile。他们有一对一的关系,但大多数时候我只需要User 数据,但它会获取(无论我尝试什么,哦,天哪,我在每个帖子上尝试了 5 页谷歌的世界建议)。

    所以我的第一个问题是,有没有办法在 JPA 和 Spring 中延迟获取一对一的关系? (因为大部分帖子都是2-3年以上)。

    我遇到的另一个问题是以“动态”方式构建 JSON 响应。我使用 Rails 做了一些事情,对 JBuilder 甚至是 to_json 非常满意,这让我能够根据控制器和我目前的需求构建 json 响应。

    在 Spring 中,我看到了以下解决方案:

    Jackson @JsonView(这并不能完全解决我的问题,因为响应不是静态的,并且不能将属性分配给多个视图(据我理解的概念)); 在响应中设置我不想要的 null 属性(使用这个,但我太丑了,看起来像一个错误的演练); 或者像我在 Rails 上构建 .json.jbuilder 一样构建 HashMap(但这会扼杀我的表现,因为有时它与很多 for 建立 json 有关系,而且这看起来像一个丑陋的演练)。

我正在寻找某天可能会遇到其中一个问题的人的一些指示,因为它让我无法解决我认为不应该这么难的问题。

编辑 1

已经尝试在@OneToOne 注释上添加optional = false,以解决@snovelli 建议的OneToOne 关系的急切负载。示例:

@OneToOne(optional=false, fetch = FetchType.LAZY)
public UserProfile getUserProfile() ... 

【问题讨论】:

您的问题包含两个不相关的问题。您应该选择一个并将另一个提取到一个单独的问题中。无论如何,这是我添加了两个答案的第一个 SO 问题。 :) 我想过这个问题,但是我有一些像这样的问题,但没有人回答,所以我发布了这个问题,因为我知道我必须提供赏金才能得到答案,而我没有在赏金上没有太多的积分,我将两者放在一个问题中:(但至少我认为这是每个不是 Spring 和 JPA 的巫师的人都可能有的两个问题。 【参考方案1】:

如果连接列不在一对一关联中的父级映射到的表中,则关联cannot be lazy。原因是JPA提供者无法确定是否创建代理,以便以后访问时加载对象,或者留下null的值。

即使关联不是可选的,JPA 提供者也必须确定关联实体实例的 id 以将其存储在代理中。因此,无论如何它都必须转到关联的表。

解决方案:

    Byte code instrumentation。但并未被广泛采用。 使用一对多并将空列表处理为null,否则使用list.get(0)。您当然可以将它封装在实体类中(getter 返回列表的唯一元素或null)。缺点是您必须将其视为 JPQL 查询中的集合。 使用@PrimaryKeyJoinColumn 代替外键。如果关联不是可选的(optional = false),则 JPA 提供者知道存在具有相同 PK 的关联子级,因此它将仅将父级的 PK 作为子级的 id 存储在代理中。显然,您不能为两个实体使用两个独立的 id 生成器,否则 PK 可能不同。如果它符合您的要求,这是最好的方法。 还要在父表中添加外键(使关系在数据库中也是双向的)。缺点是您现在基本上有两个必须维护的独立关联。此外,更新两个表而不是一个表会带来性能成本(并且外键必须可以为空)。

    将父实体映射到将父表与子表连接并包含所有父列和子表id的数据库视图:

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "<child id in the view>", insertable = false, updatable = false)
    private Child child;

【讨论】:

第三个建议效果很好。我的 Id 类型出现了问题(它说接收一个整数但期待一个 Long)但是在改变它的工作类型之后(这是我稍后会查找的东西),重要的是它工作。谢谢! @augustoccesar 不客气。请记住,您不能为 @PrimaryKeyJoinColumn 的两个实体使用两个独立的 id 生成器,关联的实体必须具有相同的 PK。 说相同的 PK 意味着在创建时两者使用相同的增量,如果 User 有一个 id == 1 那么相关的 UserProfileid == 1 但在示例中两个实体都有@GeneratedValue,还是我理解错了? @augustoccesar 实际上,该教程似乎是错误的。看看this one。这里展示了如何使用foreign id 生成策略来实现此目的。但是,我会选择的最直接的方法是完全省略 id 生成器,并使用 @MapsId 从关联实体映射 id。【参考方案2】:

关于“动态”JSON:使用DTOs。

这样做的好处是可以根据使用结果 JSON 的客户端的确切需求来定制要序列化的对象。此外,域模型(Hibernate 实体)与 JSON(反)序列化逻辑解耦,允许两者独立发展。

【讨论】:

这是一个很棒的模式!不知道那件事。在某种程度上,我正在操纵响应,但使用的是 HashMap。使用 DTO 看起来更正确和可维护。谢谢!【参考方案3】:

关于@OneToOne:您更担心数据量还是对数据库的多次查询?

在前一种情况下(如果在使用 Spring Roo 时可能),您可以尝试使用 @ManyToOne 关系建模的解决方法(一对一是多对一的一种特殊情况,不是吗)。

在后一种情况下,您可以使用@Embeddable 将像 User 这样的实体分解为多个类,同时将数据一起保存在 DB 中,因此只使用一个查询来获取它。

【讨论】:

以上是关于懒惰的一对一 Spring JPA 和构建“动态”JSON的主要内容,如果未能解决你的问题,请参考以下文章

无法懒惰地初始化角色集合,..无法初始化代理 - 无会话 - JPA + SPRING

spring data jpa关联查询(一对一对多多对多)

Spring JPA:动态提供模式名称

Spring Data JPA 的 Specifications动态查询

Spring Data JPA 中的动态查询

Spring boot JPA - 延迟加载不适用于一对一映射