知識庫 / JPA RSS 訂閱

避免 Jackson 懶加載實體字段

Jackson,JPA
HongKong
8
09:40 PM · Dec 05 ,2025

1. 簡介

本快速教程將探討如何避免在非已獲取的懶加載對象上使用 Jackson 序列化,使用 Baeldung University 域模型。我們將設置一個簡單的基於 Spring 的應用程序來演示這些原則,但無需任何 Spring 經驗即可跟上。所有示例都僅依賴於 JPA 概念。

2. 設置示例

當使用 JPA 和 Jackson 處理包含延遲關聯的實體時,一個常見的問題是序列化實體。 我們將從一個大學部門的表示開始,該部門包含許多課程:

@Entity
class Department {

    @Id
    Long id;

    String name;

    @OneToMany(mappedBy = "department")
    List<Course> courses;

    // getters and setters
}

課程 的關聯通過 部門 字段在 課程 中映射。 此外,由於許多一對多關係的默認 fetch 類型是 EAGER, 我們還需要顯式地將 fetch 類型設置為 LAZY

@Entity
class Course {

    @Id
    Long id;

    String name;

    @ManyToOne(fetch = FetchType.LAZY)
    Department department;

    // getters and setters
}

當 Jackson 序列化一個實體時,它會調用其所有 getter 方法。 如果字段仍然是惰性加載的,這會觸發額外的數據庫查詢。 並且如果持久化上下文已關閉,序列化將失敗並拋出 LazyInitializationException

3. 演示問題

為了可視化該問題,我們將創建端點以保存和檢索 部門課程 實體。

3.1. 測試 DepartmentController

讓我們從具有簡單 POST 和 GET 端點的控制器開始:

@RestController
@RequestMapping("/departments")
public class DepartmentController {

    @Autowired
    DepartmentRepository repository;
    
    @PostMapping
    Department post(@RequestBody Department department) {
        return repository.save(department);
    }

    @GetMapping("/{id}")
    Optional<Department> get(@PathVariable("id") Long id) {
        return repository.findById(id);
    }
}

即使我們不包含任何課程,嘗試檢索一個新創建的部門也會導致異常:

curl http://localhost:8080/departments/1

那是因為 Jackson 無法初始化被代理的字段:

failed to lazily initialize a collection of role: 
  com.baeldung.jacksonlazyfields.model.Department.courses: 
  could not initialize proxy - no Session

3.2. 測試 CourseController

我們將遵循相同的模式來測試一個課程控制器,它將包含簡單的 POST 和 GET 端點。 但是 這一次,我們只會針對包含部門的課程拋出異常。 我們可以通過插入一個新的部門來驗證這一點:

curl -X POST http://localhost:8080/departments\
 -H "Content-Type: application/json" \
-d '{"name":"Computer-Science"}'

然後,通過ID引用它在一個新的課程中:

curl -X POST http://localhost:8080/courses \
-H "Content-Type: application/json" \
-d '{"name":"Machine-Learning", "department":{"id":1} }'

如果我們嘗試檢索該課程,將會得到與第一個類似的異常:

could not initialize proxy [com.baeldung.jacksonlazyfields.model.Department#1]
  - no Session

讓我們看看我們有哪些選擇來避免這種情況。

4. 使用 @JsonIgnore

如果不需要序列化特定的關聯關係,則可以在字段上添加 @JsonIgnore 註解:

@JsonIgnore
@OneToMany(mappedBy = "department")
List<Course> courses;

現在,該字段已從序列化中排除:

{
  "id": 1,
  "name": "Computer-Science"
}

這對於在報文中隱藏字段非常有用。

5. 使用 Jackson 的 Hibernate 模塊

全局解決方案是為 Jackson 註冊 Hibernate 模塊,該模塊依賴於 jackson-datatype-hibernate6:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate6</artifactId>
</dependency>

在將依賴項包含到我們的 POM 中後,我們必須確保我們正在使用的 ObjectMapper 實例註冊了 Hibernate6Module

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Hibernate6Module());

這避免了懶加載;相反,Jackson現在將未獲取的關聯寫為null:

{
  "id": 1,
  "name": "Computer-Science",
  "courses": null
}

這個解決方案很棒,因為它適用於使用該 ObjectMapper 實例序列化的任何對象。

6. 使用 DTO 投影

一種繁瑣但可靠的方法是創建 DTO。 通過這種方式,我們可以完全控制序列化的內容,同時解耦持久化層,避免了引入外部庫的需求。

6.1. 創建 DTO

首先,讓我們創建一個只包含我們想要序列化的字段的記錄:

public record DepartmentDto(Long id, String name) {}

6.2. 更新 GET 端點

其次,我們將更新我們的 GET 端點,使其使用 DTO:

@GetMapping("/{id}")
Optional get(@PathVariable("id") Long id) {
    return repository.findById(id)
      .map(d -> new DepartmentDto(id, d.getName()));
}

6.3. 獲取結果

現在,Jackson 即使遇到未獲取的字段也不會出現問題:

{ 
  "id": 1, 
  "name": "Computer-Science"
}

7. 結論

在本文中,我們學習瞭如何防止 Jackson 在序列化懶加載關係時訪問未獲取的代理。最佳方法取決於我們的設計:DTOs 最乾淨,而註冊 Hibernate 模塊可以防止意外異常。僅僅添加 @JsonIgnore 確保字段永遠不會被序列化。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.