You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot 2:Jackson序列化与JPA懒加载非预期自动加载问题

Hey Eric, let's break down what's happening here and fix that unwanted lazy loading issue!

Why is the associated data being loaded automatically?

The core reason is the Open Session in View (OSIV) mechanism, which is enabled by default in Spring Boot 2.x. Simply put, OSIV keeps the Hibernate Session open from the start of an HTTP request all the way until the view rendering (Jackson JSON serialization) is complete.

Here's the difference between your two test scenarios:

  • When calling getUserById in CommandLineRunner on startup: This runs outside the HTTP request context. After the method finishes, the Session closes immediately, so the lazy-loaded employee collection never gets a chance to initialize—no extra query is triggered.
  • When accessing the controller endpoint: The OSIV interceptor opens a Session when the request comes in, and only closes it after the JSON response is fully sent to the client. So when Jackson serializes the User object and accesses the employee collection, the Hibernate Session is still alive, allowing it to automatically initialize the lazy proxy and trigger that extra SQL query. That's why you don't see the expected LazyInitializationException.

How to fix this unnecessary resource waste?

Here are several practical solutions for you to choose from:

1. Disable OSIV (the most direct root fix)

Add this configuration to your application.properties:

spring.jpa.open-in-view=false

With this, the Session will close right after your service layer method finishes. Trying to access the lazy-loaded collection during Jackson serialization will throw LazyInitializationException—this is the standard lazy loading behavior you expected.

After disabling OSIV, you can control serialization in more controlled ways:

  • Use @JsonIgnore to directly exclude the employee field (if you never need to return this data):
    @OneToMany(mappedBy="user")
    @JsonIgnore // Jackson will skip this field entirely during serialization
    private List<Employee> employee = new ArrayList<>();
    
  • Use @JsonView to control which fields are serialized:
    First define view interfaces:
    public class Views {
        public static class Basic {}
        public static class Extended extends Basic {}
    }
    
    Mark fields in your User entity with the appropriate views:
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @JsonView(Views.Basic.class)
    private Long id;
    
    @JsonView(Views.Basic.class)
    private String name;
    
    @OneToMany(mappedBy="user")
    @JsonIgnoreProperties("user")
    @JsonView(Views.Extended.class)
    private List<Employee> employee = new ArrayList<>();
    
    Specify the view in your controller:
    @GetMapping(value="/{userId}" )
    @JsonView(Views.Basic.class) // Only return basic fields, no employee data loaded
    public User getUser(@PathVariable("userId") Long id) {
        return userService.getUserById(id);
    }
    
    (Note: In Spring Boot 2.x, you don't need the outdated WebMvcConfigurerAdapter—just implement the WebMvcConfigurer interface directly, as all its methods have default implementations.)

2. Use DTOs (Data Transfer Objects)

This is a more standardized approach to avoid exposing entities directly to controllers. Create a UserDTO that only contains the fields you need to return:

public class UserDTO {
    private Long id;
    private String name;

    // Constructor or use tools like ModelMapper for conversion
    public UserDTO(User user) {
        this.id = user.getId();
        this.name = user.getName();
        // Don't include the employee field to avoid triggering lazy loading
    }

    // Getters and setters
}

Then update your service layer and controller:

// UserService
public UserDTO getUserById(Long id) {
    Optional<User> optionalUser = userRepo.findById(id);
    User user = optionalUser.get();
    logger.info("\n User -> {}", user);
    return new UserDTO(user);
}

// UserController
@GetMapping(value="/{userId}" )
public UserDTO getUser(@PathVariable("userId") Long id) {
    return userService.getUserById(id);
}

Jackson will serialize the DTO instead of the entity, so it never accesses the employee collection—no extra query is triggered.

3. Control queries actively (if you need associated data occasionally)

If you do need to return employee data in some scenarios, use a fetch join in your Repository to load data in one query (avoiding N+1 queries):

public interface UserRepository extends CrudRepository<User, Long> {
    @Query("select u from User u left join fetch u.employee where u.id = ?1")
    Optional<User> findByIdWithEmployees(Long id);
}

Call this method in your service layer to load the User and its associated Employee data in a single, efficient query.

Summary

Your problem boils down to OSIV being enabled by default, causing unexpected lazy loading initialization. Disabling OSIV is the most direct fix, and combining it with DTOs or @JsonView will let you fully control the data you return, eliminating unnecessary resource waste.

内容的提问来源于stack exchange,提问作者Eric Huang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:53:18