Spring Boot事务内懒加载属性为Null的异常问题咨询
解答
1. 为何出现NullPointerException而非LazyInitializationException?
这是JPA一级缓存特性 + Spring事务传播行为共同导致的结果:
- 你的
post()方法带有@Transactional注解,当它调用get()方法时,Spring默认的事务传播行为是REQUIRED——这意味着get()会直接复用post()的活跃事务,共享同一个EntityManager实例。 - 在
post()中执行saveAndFlush(stockItem)时,新创建的StockItem(仅设置了stockId,stock字段为null)会被存入EntityManager的一级缓存(也叫持久化上下文缓存)。 - 当
get()执行getItemsById(1L)查询时,JPA会优先从一级缓存中匹配实体。对于刚保存的那个StockItem,它会直接返回缓存中的实例,并不会用查询语句里JOIN FETCH加载的关联Stock去覆盖缓存里的stock字段。 - 遍历列表时,遇到这个缓存中的
StockItem,调用getStock()得到的就是null,自然抛出NullPointerException。
而外部直接调用get()时,会开启一个全新的事务,EntityManager的一级缓存里没有这个刚保存的StockItem,查询结果完全从数据库加载,JOIN FETCH会正确填充stock字段,因此不会出现异常。
2. 无事务时成功加载懒加载属性背后的事务机制是什么?
这得益于Spring Boot**默认启用的OpenEntityManagerInViewInterceptor(简称OMIV)**机制:
- 这个拦截器会在整个HTTP请求的处理周期内,保持
EntityManager处于打开状态。即使你的控制器方法没有标注@Transactional,只要是在当前请求的线程内访问懒加载属性,EntityManager依然可以执行额外的SQL查询来加载关联数据,因此不会抛出LazyInitializationException。 - 虽然OMIV简化了开发,但它可能带来潜在的性能问题(比如意外触发N+1查询),如果不需要这个特性,可以通过配置关闭:
spring.jpa.open-in-view=false
内容的提问来源于stack exchange,提问作者dez
相关产品推荐
相关产品推荐

