避免 Jackson 懶加載實體字段獲取

Jackson,JPA
Remote
1
06:00 PM · Nov 30 ,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
}

課程 的關係通過 department 字段在 Course 中映射的。 此外,我們必須明確設置 fetch 類型為 LAZY,因為對於 one-to-many 關係,默認是 EAGER

@Entity
class Course {

    @Id
    Long id;

    String name;

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

    // getters and setters
}

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

3. 演示問題

為了可視化問題,我們將創建端點來保存和檢索 實體。

3.1. 測試

讓我們從具有簡單 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. 測試

我們將遵循相同的模式來創建一個課程控制器,具有簡單的 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
@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.