簡體   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