我的 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 会话关闭并且不保留子实体而引起的。但是,我还没有从任何其他堆栈溢出答案中得到解决方案。
部分有效但并不理想的解决方案:
遗憾的是,这个问题并没有在我的本地环境中重现。我不懂为什么? (我明白为什么我无法复制问题,我看到 @RestController 方法默认情况下似乎是事务性的,为什么?并在我的 application.properties 文件中添加了 spring.jpa.open-in-view=false )
堆栈溢出中建议的解决方案并且我已经尝试过,
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;
}
正如您已经发现的,问题在于事务会话已关闭,当您调用 getCustomerInfoList() 时,您将收到 LazyInitializationException。
@EntityGraph("customerInfoList")
注释存储库方法 findBySessionId 来使用实体图,这将在 db 语句中包含一个联接,以便在调用 findBySessionId 时始终获取 customerInfoList。 (与 EAGER 相反,EAGER 会执行单独的查询来获取数据)
由于在实体中使用
(fetch = FetchType.LAZY)
,我也遇到了此错误。
FetchType.Lazy 意味着对象在代码中被访问之前不会被加载到 Session 上下文中。
当我们尝试使用代理对象从数据库中获取延迟加载的对象时,会发生此错误。
然后我用了
(fetch = FetchType.EAGER)
。之后,这个问题就为我解决了。