简体   繁体   English

在实体中覆盖等于导致 LazyInitializationException

[英]Override equals in Entity leads to LazyInitializationException

I setup a project using spring/spring-boot/spring-jpa/spring-mvc.我使用 spring/spring-boot/spring-jpa/spring-mvc 设置了一个项目。

I created a abstract base class for all entities我为所有实体创建了一个抽象基础 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;
    }
}

I have a 'User' and 'GroceryList' class which both extend from 'IdEntity'.我有一个“用户”和“杂货清单”class,它们都从“IdEntity”扩展而来。 The class 'GroceryList' contains a field which maps lists to users like this: class 'GroceryList' 包含一个将列表映射到用户的字段,如下所示:

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

Within a controller I have created a route which allows to create new lists:在 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");
 }

I'm using the 'getOne' method from the repository in order to save me a database lookup for the user object (or cache a instance somewhere in the session).我正在使用存储库中的“getOne”方法,以便为用户 object 保存数据库查找(或在会话中的某处缓存实例)。

The problem: as long as I'm overriding the equals method within the User class via IdEntity, this throws问题:只要我通过 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]

when trying to save the entity.尝试保存实体时。 Once I remove the equals method, everything is working as expected.一旦我删除了 equals 方法,一切都按预期工作。

Actually this makes no sense to me.其实这对我来说毫无意义。 I thought I could use 'getOne' in order to get a cheap placeholder for the User... is this wrong?我以为我可以使用“getOne”来为用户获取便宜的占位符……这是错的吗? Why is the interceptor checking whether the equals method is overriden?为什么拦截器检查equals方法是否被覆盖? Additionally the exception is pretty misleading to me...此外,这个例外对我来说非常误导......

Why is the interceptor checking whether the equals method is overriden?为什么拦截器检查equals方法是否被覆盖? Additionally the exception is pretty misleading to me此外,这个例外对我来说很误导

As a rule, when you override hashCode method, you must override equals method as well.通常,当您覆盖 hashCode 方法时,您也必须覆盖 equals 方法。 When not done, you are potentially breaking equals hashcode contract.如果没有完成,您可能会破坏 equals 哈希码合约。 Otherwise the results can be unexpected like in this case.否则,结果可能会像这种情况一样出乎意料。

So, ideally the scenarios should be with both these methods overridden or without these 2 methods overridden in the entity class.因此,理想情况下,场景应该在实体 class 中覆盖这两种方法或不覆盖这两种方法。

Under the hood, Hibernate generates a Proxy object of the pojo entity.在后台,Hibernate 生成 pojo 实体的代理 object。 It does so as to enable Lazy loading.这样做是为了启用延迟加载。 So, here we are dealing with Proxy User Object and the real User Object.因此,这里我们处理的是代理用户 Object 和真实用户 Object。

The getOne method is returning a proxy object. getOne 方法返回一个代理 object。 With reference to here -参考这里 -

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

T getOne(ID id) T getOne(ID id)

Returns a reference to the entity with the given identifier.返回对具有给定标识符的实体的引用。 Depending on how the JPA persistence provider is implemented this is very likely to always return an instance and throw an EntityNotFoundException on first access.根据 JPA 持久性提供程序的实现方式,这很可能总是返回一个实例并在首次访问时抛出 EntityNotFoundException。 Some of them will reject invalid identifiers immediately.他们中的一些人会立即拒绝无效的标识符。

Because of proxy object, the behaviour changes at run time -由于代理 object,行为在运行时发生变化 -

Scenario 1 - equals and hashCode method are overridden场景 1 - equals 和 hashCode 方法被覆盖

In this scenario, proxy delegates equals and hashCode method invocations to the real objects because they are defined.在这种情况下,代理将 equals 和 hashCode 方法调用委托给真实对象,因为它们已定义。

Scenario 2 - equals and hashCode method are not overridden场景 2 - equals 和 hashCode 方法未被覆盖

The method invocations on equals / hashCode will be on Proxy Object itself. equals / hashCode 上的方法调用将在代理 Object 本身上。 Real objects are not needed.不需要真实的对象。


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. 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

When you add a User to the HashSet, then add method invokes the hashCode method and the exception is thrown.当您将用户添加到 HashSet 时,add 方法会调用 hashCode 方法并引发异常。

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

I would prefer to move this method to a Service class if you have one and make that method Transactional.我更愿意将此方法移至服务 class 如果您有一个并将该方法设置为事务性的。

This is because of missing transaction during initialize users in Grocery class这是因为在 Grocery class 中初始化用户期间缺少事务

As Grocery have one to many relationship with users and hibernate/jpa always lazy load collection (List/Set etc) by default.由于 Grocery 与用户有一对多的关系,并且默认情况下 hibernate/jpa 总是延迟加载集合(List/Set 等)。 Instead of fetching collection with entity jpa/hibernate create proxy for it and when we perform some operation on it hibernate will do a query for collection and this needs transaction, if there will be no transaction then it will throw a LazyInitializationException, with the help of @Transactional annotation you can achieve transaction with in your required method.而不是使用实体 jpa/hibernate 为它创建代理来获取集合,当我们对其执行一些操作时,hibernate 将对集合进行查询,这需要事务,如果没有事务,那么它将抛出一个 LazyInitializationException,借助@Transactional注释你可以在你需要的方法中实现事务。

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

Also you can configure your transaction for personalized behavior check: https://www.baeldung.com/transaction-configuration-with-jpa-and-spring您还可以配置您的交易以进行个性化行为检查: https://www.baeldung.com/transaction-configuration-with-jpa-and-spring

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

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