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

问题描述 投票:0回答:2

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

@Data
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "customer")
public class Customer implements Serializable{

    @Serial
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long serialNumber;

    private Long customerId;
    private String name;



    @Column(name = "session_id", length = 128)
    private String sessionId;

    @JsonManagedReference("customer-customer_info")
    @OneToMany(targetEntity = CustomerInfo.class, mappedBy="Customer", cascade = CascadeType.ALL)
    private List<CustomerInfo> customerInfoList;

}

@Data
@Builder
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "customer_info")
public class CustomerInfo implements Serializable{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long CustomerInfoId;

    @ManyToOne
    @JsonBackReference("customer-customer_info")
    @ToString.Exclude
    @JoinColumn(name="customer_session_id", nullable=false, referencedColumnName = "session_id")
    private Customer customer;

    private String metaKey;

    @Convert(converter = Encryptor.class)
    private String metaValue;
}

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

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

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

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

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

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

Spring启动版本:2.6.2 爪哇:17

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

ConnectionServiceImpl ->

@Service(“ConnectionServiceImpl”)
public class ConnectionServiceImpl implements ConnectionService {

@Autowired
private CompletionService completionService;

@Override
public boolean test(){
  Request request = completionService.recordData(“session-id”);

    try {
      System.out.println(request.toString()); //this is working fine

      String str = GsonSerializer.getInstance().toJson(request); //this is throwing the exception
      System.out.println(str);
    }
    catch(Exception ex) {
      System.out.println(ex);
//org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.test.entity.Customer.customerInfoList, could not initialize proxy - no Session
    }
    return true;
}
}

CompletionServiceImpl->

@Service(“CompletionServiceImpl”)
public class CompletionServiceImpl implements CompletionService {

@Autowired
private CustomerInfoRepository customerInfoRepository;

@Override
public Request recordData(String session){
    return prepareData(session);
}

private Request prepareData(String session){
    Request request = new Request();
    fillData(request, session);
    return request;
}

private void fillData(Request request, String session){
    Customer customer = customerRepository.findBySessionId(sessionId);
    request.setCustomerId(customer.getCustomerId());
    request.setData(parseResponse(customer, request));
    return ;
}

private List<CustomerInfo> parseResponse(Customer customer, Request request){
    List<CustomerInfo> customerInfoList = customerInfoRepository.findBySessionId(customer.getSessionId());
    CustomerInfo customerInfo = customerInfoList.stream()
                        .filter(info -> info.getMetaKey().equalsIgnoreCase(KEY))
                        .findFirst()
                        .orElse(null);

    request.setKey(customerInfo.getMetaValue());
    return customerInfoList;
}

}

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

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


@Data
public class Request implements Serializable {

    private Long customerId;
    private List<?> data;
    private String key;
}

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

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

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

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class InfoDao implements Serializable{

    private Long CustomerInfoId;

    private String metaKey;

    private String metaValue;
}
java spring-boot hibernate lazy-loading lazy-initialization
2个回答
5
投票

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

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

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

0
投票

由于在实体中使用

(fetch = FetchType.LAZY)
,我也遇到了此错误。

FetchType.Lazy 意味着对象在代码中被访问之前不会被加载到 Session 上下文中。

当我们尝试使用代理对象从数据库中获取延迟加载的对象时,会发生此错误。

然后我用了

(fetch = FetchType.EAGER)
。之后,这个问题就为我解决了。

© www.soinside.com 2019 - 2024. All rights reserved.