如何修复 org.hibernate.LazyInitializationException:未能延迟初始化角色集合:信息,无法初始化代理 - 无会话

Abh*_*sun 4 java hibernate lazy-loading lazy-initialization spring-boot

我的 Spring Boot 项目中有一个客户和一个客户信息实体。他们有一对多的关系。

\n
@Data\n@Builder\n@Entity\n@NoArgsConstructor\n@AllArgsConstructor\n@Table(name = "customer")\npublic class Customer implements Serializable{\n\n    @Serial\n    private static final long serialVersionUID = 1L;\n\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long serialNumber;\n\n    private Long customerId;\n    private String name;\n\n\n\n    @Column(name = "session_id", length = 128)\n    private String sessionId;\n\n    @JsonManagedReference("customer-customer_info")\n    @OneToMany(targetEntity = CustomerInfo.class, mappedBy="Customer", cascade = CascadeType.ALL)\n    private List<CustomerInfo> customerInfoList;\n\n}\n\n@Data\n@Builder\n@Entity\n@NoArgsConstructor\n@AllArgsConstructor\n@Table(name = "customer_info")\npublic class CustomerInfo implements Serializable{\n\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long CustomerInfoId;\n\n    @ManyToOne\n    @JsonBackReference("customer-customer_info")\n    @ToString.Exclude\n    @JoinColumn(name="customer_session_id", nullable=false, referencedColumnName = "session_id")\n    private Customer customer;\n\n    private String metaKey;\n\n    @Convert(converter = Encryptor.class)\n    private String metaValue;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

每当我尝试使用客户实体借助 getter 函数(customer.getCustomerInfo())来获取 customerInfo 时。抛出上述异常。我在很多地方都读到,该错误是由于 JPA 会话关闭并且不保留子实体而引起的。但是,我还没有从任何其他堆栈溢出答案中得到解决方案。

\n

部分有效的解决方案,但并不理想:

\n
    \n
  1. 我将 customer_info 的 fetchType 更改为 EAGER
  2. \n
  3. 在 customer_info 表中运行单独的查询。
  4. \n
\n

遗憾的是,这个问题并没有在我的本地环境中复制。我不明白为什么?\n(我明白为什么我无法复制问题,我看到@RestController 方法默认情况下似乎是事务性的,为什么?并在我的应用程序中添加了 spring.jpa.open-in-view=false 。属性文件)

\n

堆栈溢出中建议的解决方案并且我已经尝试过,

\n
    \n
  1. 我已经尝试了对调用客户实体的服务类方法的 @Transactional 修复。但是,客户实体随后会用于其他一些功能。
  2. \n
  3. List<CustomerInfo> customerInfoList = customer.getCustomerInfoList();[这一行不会抛出错误,我有事件打印了 customerInfoList 的 className,它是 persistBag。下面的行抛出错误。]Hibernate.initialise(customerList)
  4. \n
\n

Spring 启动版本:2.6.2\nJava:17

\n

有两个服务存在此问题:ConnectionServiceImpl 和 CompletionServiceImpl。发生问题的服务类别:

\n

连接服务实现->

\n
@Service(\xe2\x80\x9cConnectionServiceImpl\xe2\x80\x9d)\npublic class ConnectionServiceImpl implements ConnectionService {\n\n@Autowired\nprivate CompletionService completionService;\n\n@Override\npublic boolean test(){\n  Request request = completionService.recordData(\xe2\x80\x9csession-id\xe2\x80\x9d);\n\n    try {\n      System.out.println(request.toString()); //this is working fine\n\n      String str = GsonSerializer.getInstance().toJson(request); //this is throwing the exception\n      System.out.println(str);\n    }\n    catch(Exception ex) {\n      System.out.println(ex);\n//org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.test.entity.Customer.customerInfoList, could not initialize proxy - no Session\n    }\n    return true;\n}\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n

CompletionServiceImpl->

\n
@Service(\xe2\x80\x9cCompletionServiceImpl\xe2\x80\x9d)\npublic class CompletionServiceImpl implements CompletionService {\n\n@Autowired\nprivate CustomerInfoRepository customerInfoRepository;\n\n@Override\npublic Request recordData(String session){\n    return prepareData(session);\n}\n\nprivate Request prepareData(String session){\n    Request request = new Request();\n    fillData(request, session);\n    return request;\n}\n\nprivate void fillData(Request request, String session){\n    Customer customer = customerRepository.findBySessionId(sessionId);\n    request.setCustomerId(customer.getCustomerId());\n    request.setData(parseResponse(customer, request));\n    return ;\n}\n\nprivate List<CustomerInfo> parseResponse(Customer customer, Request request){\n    List<CustomerInfo> customerInfoList = customerInfoRepository.findBySessionId(customer.getSessionId());\n    CustomerInfo customerInfo = customerInfoList.stream()\n                        .filter(info -> info.getMetaKey().equalsIgnoreCase(KEY))\n                        .findFirst()\n                        .orElse(null);\n\n    request.setKey(customerInfo.getMetaValue());\n    return customerInfoList;\n}\n\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n

当我在调试模式下运行代码时,我在断点处看到此错误,\n方法抛出 \'org.hibernate.LazyInitializationException\' 异常。无法评估 com.test.entity.Customer.toString()

\n

导致整个问题的请求类Request序列化是:

\n
\n@Data\npublic class Request implements Serializable {\n\n    private Long customerId;\n    private List<?> data;\n    private String key;\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n

我发现的唯一似乎是一个昂贵的操作的解决方案是通过 BeanUtils 将数据 customerInfoList 转换为 InfoDao,以便将 CustomerInfoList 的所有字段复制到 InfoDao(没有 Customer 字段)。请提出更好的解决方案。

\n
private List<InfoDao> convertToArray(List<?> list){\n  List<InfoDao> list1 = new ArrayList<>();\n  for(Object v:list){\n    InfoDao infoDao = new InfoDao();\n    BeanUtils.copyProperties(v,infoDao);\n    list1.add(infoDao);\n  }\n  return list1;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

InfoDao 与 CustomerInfo 具有相同的架构,但没有 Customer 引用。

\n
@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class InfoDao implements Serializable{\n\n    private Long CustomerInfoId;\n\n    private String metaKey;\n\n    private String metaValue;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

tbj*_*rch 5

正如您已经发现的,问题在于事务会话已关闭,并且当您调用 getCustomerInfoList() 时,您将收到 LazyInitializationException。

解决这个问题的几种可能的方法(好的和不太好的):

  1. 使用 open-in-view=true,会话将在请求的生命周期中保持活动状态,允许 GsonSerializer 执行数据库查询来获取数据
  2. 在与 customerInfoList 的关系上设置获取类型 EAGER(将始终执行单独的查询以从数据库获取 CustomerInfoList 数据)
  3. 通过注释存储库方法 findBySessionId 来使用实体图@EntityGraph("customerInfoList"),该方法将在 db 语句中包含一个联接,以便在调用 findBySessionId 时始终获取 customerInfoList。(与 EAGER 相反,EAGER 会执行单独的查询来获取数据)
  4. 执行 1 个事务内的所有逻辑以保持会话处于活动状态。
  5. 使用 JPA 投影并将数据库数据投影到 DTO(在您不打算更改实体状态而只是使用值的情况下,这是一个选项)