使用Hibernate和Spring Boot时,FetchType.LAZY会导致NullPointerException发生。

4

更新

我想指出@sainr的回答“将Hibernate代理转换为真正的实体对象”确实解决了这个问题。但幕后问题是我的SiteEntitysetControllerEntitygetControllerEntity使用了final修饰符,这一点我没有在我的问题中提到。我很抱歉。

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

关于解释可以在Stack Overflow上的另一个答案中找到。


我有三个如下的实体

@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中的nullcontrollerEntity引起的。
此外,即使我尝试使用siteRepositoy重新获取siteEntity,它的controllerEntity仍然为null
当我从DeviceEntity和SiteEntity两个类中都去掉了fetch=FetchType.LAZY时,NPE不再发生。
但这似乎很奇怪,毫无意义。我能在期望hibernate获取正确值的同时使用FetchType.LAZY吗?
谢谢。
2个回答

3
为了让您能够访问使用FetchType.LAZY声明的字段,Hibernate使用CGLIB构建代理。因此,当您调用这样的字段的getter(在您的情况下为getSiteEntity()getControllerEntity())时,您不是直接访问字段值--相反,调用会传递给Hibernate的代理对象。然后,Hibernate尝试从数据存储加载实际值,为此,它需要一个活动的Hibernate会话来访问数据库。在您的情况下,Hibernate会话很可能已经关闭,这样懒加载就会失败,并给您一个有效的null字段值。
基本上有两种方法可以解决这个问题:
  1. 使用FetchType.EAGER,它将与持有对象DeviceEntity一起加载所有字段值
  2. 将代理对象转换为真实对象(请查看将Hibernate代理转换为真实实体对象),并以常规方式访问它
考虑一下,您是否真的需要在您的情况下进行懒加载。如果您没有在子字段中存储大量重型对象以按需加载它们,则可能切换到FetchType.EAGER将是最简单的方法。
希望这可以帮助您。

1
谢谢。实际上它并不包含重型对象。我将切换到EAGER。然而,如果需要FetchType.LAZY,例如重型对象或ManyToMany、OneToMany映射,有没有办法防止Hibernate会话关闭或者“将代理对象转换为真实对象”是正确的方法?谢谢。 - Chiu
1
@Chiu,你可以尝试在TransactionTemplate中使用(或者标记一个方法,在该方法中加载持有者对象并访问延迟加载的子对象时加上@Transactional)。 - Vladimir Salin
1
我想指出,“将Hibernate代理转换为真实的实体对象”确实解决了这个问题。但幕后的问题是我的SiteEntitysetControllerEntitygetControllerEntity上有一个final修饰符。移除final修饰符,然后Hibernate就可以很好地初始化代理对象了。 - Chiu

2
Hibernate有时对原始类型的处理效果不佳。尝试替换。
private long id

为了

private Long id

在Hibernate中,对于主键最好使用包装类而不是基本数据类型。


谢谢!我发现最近研究了一下Hibernate如何处理原始类型的空值。 - Chiu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接