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
getUserByIdinCommandLineRunneron startup: This runs outside the HTTP request context. After the method finishes, the Session closes immediately, so the lazy-loadedemployeecollection 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
employeecollection, 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 expectedLazyInitializationException.
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
@JsonIgnoreto directly exclude theemployeefield (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
@JsonViewto control which fields are serialized:
First define view interfaces:
Mark fields in your User entity with the appropriate views:public class Views { public static class Basic {} public static class Extended extends Basic {} }
Specify the view in your controller:@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<>();
(Note: In Spring Boot 2.x, you don't need the outdated@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); }WebMvcConfigurerAdapter—just implement theWebMvcConfigurerinterface 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

