FetchType.LAZY 不适用于@OneToOne 关系

Posted

技术标签:

【中文标题】FetchType.LAZY 不适用于@OneToOne 关系【英文标题】:FetchType.LAZY not working for @OneToOne relation 【发布时间】:2022-01-21 15:12:50 【问题描述】:

我正在使用 JPA2 和 Springboot2X。试图在UserAccount 表中定义简单的一对一关系。 User 是父级,Account 是子级。这种关系是从父母到孩子的。已关注this。

User 表有一个列account_no 来保存帐号,它是Account 表的主键。所以User 持有外键。

@Entity
public class Account extends AbstractEntity implements Serializable

    // fields like account#, balance etc goes here..

    @OneToOne (fetch = FetchType.LAZY)
    private User user;



@Entity
public class User extends AbstractEntity implements Serializable

    // Other fields related to user entity go here ..

    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "account_no", referencedColumnName = "account_number")
    private Account account;

保存工作正常。但是当我获取用户(User user = userRp.findByUserID(userid);)时,我会从两个表中获取数据。我的问题是当我定义fetch = FetchType.LAZY 时,为什么要获取帐户表数据?我只希望用户数据出现在输出中,就像我在 User 表中运行选择查询时一样。

输出:

    
    "id": 1,
    "userID": "xxxxx",
    "firstName": "Dsd",
    "llastName": "cccc",
    "email": "danqsaa@brown",
    "creation_date": "2021-12-19 19:41",
    "modified_date": "2021-12-19 19:41",
    "account": 
        "id": 2,           
        "accountNo": 6848982326,
        "balance": 4000.0,
        "creationUser": "xxx",
        "creation_date": "2021-12-19 19:41",
        "modified_date": "2021-12-19 19:41",
        "transactions": []
    

数据库输出

在 RestController、Service 和 Repo 详细信息下方添加。

@RestController
public class UserController 

    @GetMapping("/users/userID")
        public ResponseEntity<Object> getUser(@PathVariable("userID") String userID)
                   return new ResponseEntity<>(userServiceImpl.getUserByID(userID), HttpStatus.OK)); 
        


@Service
public class UserService 

 // injected user repo
public User getUserByID(String userID)
        return  userRepo.findByUserID(userID);      
    


public interface UsersRepository extends CrudRepository<User, String> 
     User findByUserID(String userID);

【问题讨论】:

您的Output 那里-您如何将数据序列化到该对象中?还是只是从您的 select 语句中返回的 json。 @cjnash 这是我的休息控制器的输出。来自邮递员。 这里只是一个想法,也许当对象被序列化时,getter() 被调用,从而触发了加载。您可以通过调试和检查查询返回的内容与您通过休息返回的内容来检查这一点。不过我可能是错的。 不明白你的问题——你期待什么? Jackson 将访问该属性,这会导致关联加载。你期待别的吗?究竟是什么?另外,你的@Transactional在哪里? 【参考方案1】:

我以为我有这个问题的答案,认为只是杰克逊在响应对象的序列化过程中调用了 getter,结果获取了惰性属性,但似乎情况并非如此!!

这个问题是从文档中挖掘和学习一些奇怪东西的好理由。

最常见的 ORM 供应商是休眠​​。因此,如果您阅读有关 hibernate 的文档,它会明确指出 EAGER 类型是 Hibernate 必须遵循的但 LAZY fetch 类型只是暗示 hibernate 可能会或可能不会遵循 > !!

来自hibernate doc

fetch - FetchType

(默认为 EAGER)定义此属性是否 应该急切地或懒惰地获取。 JPA 说 EAGER 是 要求提供者(Hibernate)的值应该是 获取所有者时获取,而 LAZY 只是一个提示 访问属性时获取该值休眠忽略 除非您使用字节码,否则此设置适用于基本类型 增强。有关更多信息,请参阅字节码增强 关于获取和字节码增强。

另外,根据文档,您至少现在必须遵循字节码增强功能,以便延迟加载工作

作为一个有希望的临时遗留保留,目前是必需的 所有惰性单数关联(多对一和一对一)也 包括 @LazyToOne(LazyToOneOption.NO_PROXY)。计划是放松 以后有这个要求。

另外根据hibernate performance enhancement doc,你还需要一个hibernate的配置属性,所以在Spring Boot App中,还请在你的application.yml或属性文件中包含以下内容。

hibernate.enhancer.enableLazyInitialization = true

包含此属性后,您的实体必须更新为

@Entity
public class User extends AbstractEntity implements Serializable

    // Other fields related to user entity go here ..

    @LazyToOne(LazyToOneOption.NO_PROXY)  <------
    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "account_no", referencedColumnName = "account_number")
    private Account account;


@Entity
public class Account extends AbstractEntity implements Serializable

    // fields like account#, balance etc goes here..

    @LazyToOne(LazyToOneOption.NO_PROXY) <-----
    @OneToOne (fetch = FetchType.LAZY)
    private User user;


所有这些都是hibernate启用延迟加载功能所需要的。

那么您仍然会在您的响应中看到它,因为 Jackson 在序列化时会调用 getter 方法,因此它将强制休眠来获取值。所以你还需要通知jackson在序列化过程中不要使用这个getter。

所以你也必须更新

  @Entity
  public class User extends AbstractEntity implements Serializable

    @JsonIgnore <--------
    @JsonProperty(access =  JsonProperty.Access.WRITE_ONLY) <-------
    @LazyToOne(LazyToOneOption.NO_PROXY) 
    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "account_no", referencedColumnName = "account_number")
    private Account account; 

希望您能因为延迟加载而看不到帐户响应。

【讨论】:

这不起作用。我仍然在邮递员中获取 Account 对象 @everalian 你的控制器是什么?请使用相关代码更新问题 更新了剩余代码。谢谢。 @everalian 请检查更新的答案 不确定您的答案是否有效。但是,当我只使用@JsonProperty(access = JsonProperty.Access.WRITE_ONLY) 时,它可以工作。就像这样 - ***.com/questions/49469715/…【参考方案2】:

很遗憾,这个问题具有误导性,并且需要许多 cmets 才能找出您要问的内容。最后我明白了 - 你看到 JSON 中的字段并且你不想看到。这是一个纯粹与 Jackson 相关的问题,与 Hibernate 无关。

您可以采取多种方式:

使用 DTO

我们不使用实体作为 JSON 序列化的一部分是有充分理由的 - 正是因为它们很少能满足我们的 JSON 要求。

因此,我们创建了一个特殊的 DTO 类,它准确地映射了我们对特定端点的需求。我们将 Entity 转换为 DTO(它不会包含 account 字段)然后序列化 DTO。

使用杰克逊视图

Jackson 有一个JsonView 的概念——它允许您根据请求的视图以不同的方式映射同一个对象。这样,您可以使 account 字段对该特定端点不可见。

忽略属性

正如其他回复中已经暗示的那样 - 您可以 @JsonIgnore 该属性或取消其可见性或使用 WRITE_ONLY 使其仅反序列化。

杰克逊休眠模块

Jackson 有专门针对 Hibernate 的“插件”:jackson-datatype-hibernate。它们允许配置为忽略惰性字段而不初始化它们。在这种情况下,Jackson 将序列化 null (HibernateXModule.Feature)。

这可能是最糟糕的选择,因为您真的不想将如何优化您的工作与 DB 和您的表示层联系起来。

【讨论】:

我不确定你在这里建议什么? 我试图解释你看到的行为是预期的。从问题中不清楚为什么你认为它不是预期的。 你是想说这是正常行为吗?好吧,那么如何避免获取子数据呢?唯一可行的方法是添加@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)。但这并不会停止调用与用户表的左外连接的第二个选择查询。这意味着,休眠调用仍在发生以获取帐户详细信息,但仅未显示在其余响应中。 现在很清楚您需要什么了。更新了答案。 感谢您的更新。这个问题提到了我对 json 输出的问题。当我发布它时,我不确定问题在哪里。但是,在大家的帮助下,我发现是 Jackson 导致了初始化。

以上是关于FetchType.LAZY 不适用于@OneToOne 关系的主要内容,如果未能解决你的问题,请参考以下文章

JPA fetchType.Lazy 不工作

FetchType.LAZY和FetchType.EAGER什么区别

没有会话的休眠 FetchType.LAZY

JPA。 FetchType.Lazy 引起了奇怪的行为@ManyToOne

Hibernate:为啥 FetchType.LAZY 注释的集合属性急切地加载?

Spring Boot 和 fetchType=Lazy