SpringMVC中如何避免Jackson序列化MyBatis懒加载对象?
这个问题我之前也碰到过,核心原因其实很直白:Jackson在序列化Java对象时,会反射调用所有的getter方法,而你开启了MyBatis的懒加载,book.getAuthor()刚好就是触发懒加载的入口,所以序列化过程中自动就把Author给查出来了。下面给你几个实用的解决方案,按推荐程度排序:
1. 最推荐:使用DTO(数据传输对象)
这是最规范的做法,从根源上避免实体类和API返回结构耦合。
- 新建一个
BookDTO类,只定义你需要返回给前端的字段(比如id、bookName、isbn),完全不包含author相关的属性。 - 在Service层,把MyBatis查询出来的
Book实体对象转换为BookDTO(可以用MapStruct、ModelMapper这类工具简化转换,或者手动set字段)。 - 控制器返回
BookDTO对象,Jackson序列化时只会处理DTO里的字段,根本不会碰getAuthor()方法,自然不会触发懒加载。
如果某些场景下需要返回Author信息,可以再建一个BookWithAuthorDTO,包含authorName等需要的字段,灵活应对不同的API需求。
2. 用Jackson注解直接控制序列化
如果不想额外写DTO,可以用Jackson的注解来阻止序列化author属性:
方式一:在
getAuthor()方法上加@JsonIgnorepublic class Book { // 其他字段和方法 @JsonIgnore public Author getAuthor() { return author; } }这样Jackson序列化时会跳过这个getter,也就不会触发懒加载。缺点是这个配置是全局的,所有场景下都不会返回Author信息。
方式二:用
@JsonIgnoreProperties在类上指定忽略的属性@JsonIgnoreProperties({"author"}) public class Book { // 类内容 }和上面效果类似,更简洁地指定要忽略的属性。
方式三:用
@JsonView做场景化控制
如果有时候需要返回Author,有时候不需要,可以定义视图接口:public interface Views { interface Basic {} interface WithAuthor extends Basic {} }然后在Book类的字段/getter上标注:
public class Book { @JsonView(Views.Basic.class) private Long id; @JsonView(Views.Basic.class) private String bookName; @JsonView(Views.WithAuthor.class) private Author author; // 对应的getter也要加@JsonView }控制器方法上指定要使用的视图:
@GetMapping("/books/{id}") @JsonView(Views.Basic.class) public Book getBook(@PathVariable Long id) { return bookService.getBookById(id); }这样只有指定
WithAuthor视图的接口才会序列化Author,触发懒加载,其他接口用Basic视图就不会。
3. 控制MyBatis懒加载的触发时机
这种方式适合不想改实体类和DTO的场景,但需要注意线程安全:
- 可以在控制器返回对象前,手动关闭当前线程的MyBatis懒加载。比如利用MyBatis的
SqlSession:
不过这种方式有点hack,不如前两种优雅,而且要注意线程隔离的问题。@Autowired private SqlSessionFactory sqlSessionFactory; @GetMapping("/books/{id}") public Book getBook(@PathVariable Long id) { Book book = bookService.getBookById(id); // 关闭当前线程的懒加载,避免序列化时触发 SqlSession sqlSession = sqlSessionFactory.openSession(); try { sqlSession.clearCache(); // 或者临时设置懒加载为false sqlSession.getConfiguration().setLazyLoadingEnabled(false); } finally { sqlSession.close(); } return book; }
另外还有个小技巧:如果你的MyBatis懒加载用的是CGLIB代理,可以在序列化前判断是否是未加载的代理对象,手动转为null。比如用MyBatis的org.apache.ibatis.executor.loader.LoaderUtil来判断,然后定制Jackson的序列化器,但实现起来稍复杂,不如前面的方案直接。
内容的提问来源于stack exchange,提问作者YM cho

