繁体   English   中英

在实体中覆盖等于导致 LazyInitializationException

[英]Override equals in Entity leads to LazyInitializationException

我使用 spring/spring-boot/spring-jpa/spring-mvc 设置了一个项目。

我为所有实体创建了一个抽象基础 class

@MappedSuperclass
public abstract class IdEntity implements Persistable<Long> {

    @Id
    @Column(unique = true, nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    public Long getId() {
        return id;
    }

    protected void setId(Long id) {
        this.id = id;
    }

    @Transient
    public boolean isNew() {
        return null == getId();
    }

    @Override
    public boolean equals(Object obj) {

        if (null == obj) {
            return false;
        }

        if (this == obj) {
            return true;
        }

        if (!(obj instanceof IdEntity)) {
            return false;
        }

        IdEntity that = (IdEntity) obj;

        return null != this.getId() && this.getId().equals(that.getId());
    }

    @Override
    public int hashCode() {
       int hashCode = 17;

       hashCode += null == getId() ? 0 : getId().hashCode() * 31;

       return hashCode;
    }
}

我有一个“用户”和“杂货清单”class,它们都从“IdEntity”扩展而来。 class 'GroceryList' 包含一个将列表映射到用户的字段,如下所示:

@ManyToMany
@JoinTable(name = "users_grocerylists",
           joinColumns = {@JoinColumn(name = "grocerylists_id")},
           inverseJoinColumns = {@JoinColumn(name = "users_id")})
private Set<User> users = new HashSet<>();

在 controller 中,我创建了一条允许创建新列表的路线:

 @PostMapping(value = "/grocery-list/new")
 public void newGroceryList(@RequestParam("name") String name, HttpServletResponse response) throws IOException {
     UserPrincipal currentUser = getUserPrincipalOrThrow();

     GroceryList groceryList = new GroceryList();
     groceryList.setName(name);
     groceryList.getUsers().add(userRepository.getOne(currentUser.getUserId()));

     groceryListRepository.save(groceryList);

     response.sendRedirect("/grocery-lists");
 }

我正在使用存储库中的“getOne”方法,以便为用户 object 保存数据库查找(或在会话中的某处缓存实例)。

问题:只要我通过 IdEntity 覆盖用户 class 中的 equals 方法,就会抛出

org.hibernate.LazyInitializationException: could not initialize proxy [grocery.model.User#1] - no Session
    at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:169) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
    at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:309) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
    at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
    at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
    at grocery.model.User$HibernateProxy$xc99OVLQ.hashCode(Unknown Source) ~[main/:na]
    at java.util.HashMap.hash(HashMap.java:339) ~[na:1.8.0_252]
    at java.util.HashMap.put(HashMap.java:612) ~[na:1.8.0_252]
    at java.util.HashSet.add(HashSet.java:220) ~[na:1.8.0_252]

尝试保存实体时。 一旦我删除了 equals 方法,一切都按预期工作。

其实这对我来说毫无意义。 我以为我可以使用“getOne”来为用户获取便宜的占位符……这是错的吗? 为什么拦截器检查equals方法是否被覆盖? 此外,这个例外对我来说非常误导......

为什么拦截器检查equals方法是否被覆盖? 此外,这个例外对我来说很误导

通常,当您覆盖 hashCode 方法时,您也必须覆盖 equals 方法。 如果没有完成,您可能会破坏 equals 哈希码合约。 否则,结果可能会像这种情况一样出乎意料。

因此,理想情况下,场景应该在实体 class 中覆盖这两种方法或不覆盖这两种方法。

在后台,Hibernate 生成 pojo 实体的代理 object。 这样做是为了启用延迟加载。 因此,这里我们处理的是代理用户 Object 和真实用户 Object。

getOne 方法返回一个代理 object。 参考这里 -

https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html#getOne-ID-

T getOne(ID id)

返回对具有给定标识符的实体的引用。 根据 JPA 持久性提供程序的实现方式,这很可能总是返回一个实例并在首次访问时抛出 EntityNotFoundException。 他们中的一些人会立即拒绝无效的标识符。

由于代理 object,行为在运行时发生变化 -

场景 1 - equals 和 hashCode 方法被覆盖

在这种情况下,代理将 equals 和 hashCode 方法调用委托给真实对象,因为它们已定义。

场景 2 - equals 和 hashCode 方法未被覆盖

equals / hashCode 上的方法调用将在代理 Object 本身上。 不需要真实的对象。


You are getting this exception in 1st scenario as there is no Hibernate session available there and before it can invoke methods on real User Object, it must first load it using Hibernate Session.

org.hibernate.LazyInitializationException: could not initialize proxy [grocery.model.User#1] - no Session

当您将用户添加到 HashSet 时,add 方法会调用 hashCode 方法并引发异常。

groceryList.getUsers().add(userRepository.getOne(currentUser.getUserId()));

我更愿意将此方法移至服务 class 如果您有一个并将该方法设置为事务性的。

这是因为在 Grocery class 中初始化用户期间缺少事务

由于 Grocery 与用户有一对多的关系,并且默认情况下 hibernate/jpa 总是延迟加载集合(List/Set 等)。 而不是使用实体 jpa/hibernate 为它创建代理来获取集合,当我们对其执行一些操作时,hibernate 将对集合进行查询,这需要事务,如果没有事务,那么它将抛出一个 LazyInitializationException,借助@Transactional注释你可以在你需要的方法中实现事务。

@Transactional
 @PostMapping(value = "/grocery-list/new")
 public void newGroceryList(@RequestParam("name") String name, HttpServletResponse response) throws IOException {
   //your code
 }

您还可以配置您的交易以进行个性化行为检查: https://www.baeldung.com/transaction-configuration-with-jpa-and-spring

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM