Spring JPA could not initialize proxy - no Session
JPA 通过谓词分析的方式来快速地实现数据库访问相关的操作,优点是提高了开发速度,但是如果遇到问题,排查起来会很麻烦
本次遇到的问题如下图所示:
Exception in thread "main" org.hibernate.LazyInitializationException: could not initialize proxy [org.xhliu.demo.entity.CustomerInfo#1] - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:176)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:322)
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45)
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95)
at org.xhliu.demo.entity.CustomerInfo$HibernateProxy$zwCU7ed1.toString(Unknown Source)
at org.xhliu.demo.Application.main(Application.java:29)
这种异常一般发生在使用 JPA 执行一对多的连接查询的情况,这是由于在执行查询时,本次一对多的实体类字段属性可能未完成初始化,如 List
、Collection
等,而此时 JPA 的持久化上下文已经被关闭了,因此抛出了 “no session” 这样的异常
解决方案:
-
添加全局 JPA 配置
enable_lazy_load_no_trans
本解决方案极度不推荐
在全局配置文件中添加如下的配置项:
spring: jpa: properties: hibernate: enable_lazy_load_no_trans: true
如果配置了这个属性,那么 JPA 就会为每一个一对多的关联字段分配一个单独的临时 Session,使得这个字段能够通过这个临时会话拿到对应的属性。
这是一种 ”反模式“,因为如果这么配置了,当一对多的字段属性变多时,为了维护这些临时 Session 将会给底层的连接池带来很大的压力,严重的话将会明显地影响到性能,如果有别的解决方案尽量不要使用该方案
-
定义数据拉取策略
JPA 定义了如下的获取列数据的策略:
public enum FetchType { // 该策略表示数据可以被延迟获取 LAZY, // 该策略表示必须被尽早地获取 EAGER }
对于一对多的集合集合属性字段,使用
EAGER
来确保能够即使获取数据,从而解决这个问题,具体使用如下所示:@Entity @Table(name = "user_info") public class UserInfo { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "user_id", nullable = false) private long userId; @Basic(fetch = FetchType.LAZY) @Column(name = "user_phone") private String userPhone; @OneToMany(fetch = FetchType.EAGER) private Set<Feature> holdingFeatures = new HashSet<>(); @OneToMany(fetch = FetchType.EAGER) private Set<Book> bookSet = new HashSet<>(); }
-
@NamedEntityGrap
和@EntityGraph
具体使用如下所示:
对于实体
Item
:@Entity @NamedEntityGraph( name = "Item.characteristics", attributeNodes = @NamedAttributeNode("characteristics") ) public class Item { @Id private Long id; private String name; @OneToMany(mappedBy = "item") private List<Characteristic> characteristics = new ArrayList<>(); }
对于实体
Characteristic
:@Entity public class Characteristic { @Id private Long id; private String type; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn private Item item; }
对于 JPA 接口
ItemRepository
:public interface ItemRepository extends JpaRepository<Item, Long> { @EntityGraph(value = "Item.characteristics") Item findByName(String name); }
对于 JPA 接口
CharacteristicsRepository
:public interface CharacteristicsRepository extends JpaRepository<Characteristic, Long> { @EntityGraph(attributePaths = {"item"}) Characteristic findByType(String type); }
这样也可以解决由于存在的级联关系导致的 “no session” 的问题
参考:
[1] https://stackoverflow.com/questions/36583185/spring-data-jpa-could-not-initialize-proxy-no-session-with-methods-marke
[2] https://www.baeldung.com/spring-data-jpa-named-entity-graphs