【问题标题】:NullPointerException happens with FetchType.LAZY withi hibernate and spring bootNullPointerException 发生在 FetchType.LAZY withi hibernate 和 spring boot
【发布时间】:2018-06-07 19:41:55
【问题描述】:

更新

我想指出@sainr 的回答Converting Hibernate proxy to real entity object 确实解决了这个问题。但幕后的问题是我的SiteEntity 有一个final 修饰符,它是setControllerEntitygetControllerEntity,我没有在我的问题中提出。我很抱歉。

删除final 修饰符。然后Hibernate就可以很好的初始化代理对象了。

可以在 Stack Overflow 上的另一个 answer 中找到解释。


我有以下三个实体

@Entity
@Table(name = "controller")
public class ControllerEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(nullable = false, updatable = false)
    private long id;
}

@Entity
@Table(name = "site")
public class SiteEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(nullable = false)
    private long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "controller_id", nullable = false)
    private ControllerEntity controllerEntity;
}

@Entity
@Table(name = "device")
public class DeviceEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(nullable = false)
    private long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "site_id", nullable = true)
    private SiteEntity siteEntity;
}

找到设备实体后,我尝试直接从中获取controllerEntity

final DeviceEntity deviceEntity1 = deviceRepository.findOne(1L);
System.out.println(deviceEntity1.getSiteEntity().getControllerEntity().getId());

但它会产生一个java.lang.NullPointerException,这是由siteEntity 中的null controllerEntity 引起的。

此外,即使我尝试使用 siteRepositoy 再次获取 siteEntity。它的controllerEntity 仍然是null

在我从 DeviceEntity 和 SiteEntity 中删除 fetch = FetchType.LAZY 后,NPE 不再发生。

但这似乎很奇怪,没有意义。我可以使用FetchType.LAZY 同时期待休眠获取正确的值吗?

谢谢。

【问题讨论】:

    标签: java hibernate spring-boot lazy-loading


    【解决方案1】:

    为了让您可以访问使用FetchType.LAZY 声明的字段,Hibernate 使用 CGLIB 构造了一个代理。因此,当您为此类字段(在您的情况下为getSiteEntity()getControllerEntity())调用getter 时,您不会直接访问字段值——而是将调用传递给Hibernate 的代理对象。反过来,Hibernate 尝试从数据存储中加载实际值,为此,它需要一个活动的 Hibernate 会话来访问数据库。最有可能的是,在您的情况下,Hibernate 会话已经关闭并且这种延迟加载失败,从而为您提供有效的 null 字段值。

    基本上有两种方法可以解决这个问题:

    1. 使用FetchType.EAGER,它将加载所有字段值以及持有对象DeviceEntity
    2. 将代理对象转换为真实对象(检查Converting Hibernate proxy to real entity object)并以常规方式访问它

    考虑一下,您是否真的需要延迟加载。如果您没有在子字段中存储大量重物以按需加载它们,那么切换到FetchType.EAGER 可能是最简单的方法。

    希望对您有所帮助。

    【讨论】:

    • 谢谢。它实际上不包含重物。我会改用 EAGER。但是,如果需要 FetchType.LAZY,例如重对象或 ManyToMany、OneToMany 映射,是否有防止关闭休眠会话或“将代理对象转换为真实对象”的方法?谢谢
    • @Chiu 您可以尝试在TransactionTemplate 中使用(或者将您正在加载持有者对象并访问惰性子对象的方法标记为@Transactional
    • 我想指出“将 Hibernate 代理转换为真实实体对象”确实解决了这个问题。但幕后的问题是我的SiteEntity 有一个final 修饰符setControllerEntitygetControllerEntity。删除 final 修饰符。那么Hibernate就可以很好的初始化代理对象了。
    【解决方案2】:

    Hibernate 有时不能很好地处理原始类型。尝试替换

    private long id
    

    private Long id
    

    对于 Hibernate 中的主键,最好使用包装类而不是原始类型。

    【讨论】:

    • 谢谢!我确实发现在最近关于休眠如何处理原始类型的空值的研究中
    猜你喜欢
    • 1970-01-01
    • 2017-10-01
    • 2012-06-22
    • 1970-01-01
    • 2020-01-04
    • 2020-10-27
    • 2013-02-27
    • 2018-11-30
    • 1970-01-01
    相关资源
    最近更新 更多