Spring Data JPA撈取一對多(One To Many)物件時,發生錯誤org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.abc.demo.entity.Department.employeeList, could not initialize proxy - no Session
。
entity關係為一個Department
對多個Employee
設定如下。
Department
package com.abc.demo.entity;
import lombok.*;
import javax.persistence.*;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Department implements Serializable {
private static final Long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy="department")
private List<Employee> employeeList;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Department that = (Department) o;
return Objects.equals(id, that.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString() {
return "Department{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
Employee
package com.abc.demo.entity;
import lombok.*;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Objects;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Employee implements Serializable {
private static final Long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name="DEPARTMENT_ID") // 外鍵欄位名稱
private Department department;
private String name;
private Integer age;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return Objects.equals(id, employee.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", department=" + department +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
使用下面findById_employeeListHasValue()
測試時撈取Department
的employeeList
並調用其方法時出現錯誤。
DepartmentRepositoryTests
package com.abc.demo.repository;
import com.abc.demo.entity.Department;
import com.abc.demo.entity.Employee;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Collections;
import java.util.List;
@SpringBootTest
class DepartmentRepositoryTests {
@Autowired
private DepartmentRepository departmentRepository;
@Test
void findById_hasValue() {
Department department = departmentRepository.findById(1L)
.orElse(null);
Assertions.assertNotNull(department);
}
@Test
void findById_employeeListHasValue() {
List<Employee> employeeList = departmentRepository.findById(1L)
.map(Department::getEmployeeList)
.orElse(Collections.emptyList());
Assertions.assertEquals(2, employeeList.size()); /* throw LazyInitializationException:
failed to lazily initialize a collection of role: com.abc.demo.entity.Department.employeeList,
could not initialize proxy - no Session */
}
}
出現LazyInitializationException
是因為在Hibernate的persistence context的session外試圖取得懶加載(lazy-load)的物件employeeList
。
在findById_employeeListHasValue()
中取得employeeList
時@OneToMany
的fetch
屬性預設為javax.persistence.FetchType.LAZY
,所以到此Hibernate都尚未去資料庫取得資料,直到調用了employeeList
的方法才去資料庫取資料。而findById_employeeListHasValue()
方法無交易管理,所以取得employeeList
後即離開了persistence context,導致在呼叫employeeList.size()
發生此錯誤。
解決方法一為在findById_employeeListHasValue()
前掛上@Transactional
,將整個方法設為交易,在方法結束前不會離開persistence context的session。
@Transactional // <--
@Test
void findById_employeeListHasValue() {
List<Employee> employeeList = departmentRepository.findById(1L)
.map(Department::getEmployeeList)
.orElse(Collections.emptyList());
Assertions.assertEquals(2, employeeList.size());
}
解決方法二為把Department.employeeList
前的@OneToMany
的fetch
設為FetchType.EAGER
,也就是立即載入關聯物件。但這樣就會沒有懶加載的效果並導致效能低落,由其是多邊物件數量很多的時候。
Department
public class Department implements Serializable {
...
@OneToMany(mappedBy="department", fetch = FetchType.EAGER)
private List<Employee> employeeList;
...
}
解決方法三為在配置檔設定spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
。作用為每次在取得lazy-load物件時,會自動開啟一個新的session來取得。
application.properites
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
解決方法四為在Repository使用JPQL的JOIN FETCH
查詢。
DepartmentRepository
package com.abc.demo.repository;
import com.abc.demo.entity.Department;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface DepartmentRepository extends JpaRepository<Department, Long> {
@Query("SELECT d FROM Department d " +
" JOIN FETCH d.employeeList " +
" WHERE d.id = :id")
@Override
Optional<Department> findById(@Param("id") Long id);
}
沒有留言:
張貼留言