我有三个由ManyToOne关系相关的实体:
@Entity
@Table(name = "cabin_layout")
@Getter
@Setter
@NoArgsConstructor
@ToString
public class CabinLayout {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@JsonIgnore
@NotNull
@ManyToOne(optional = false)
private MissionProfile missionProfile;
}
@Entity
@Table(name = "mission_profile")
@Getter
@Setter
@NoArgsConstructor
@ToString
public class MissionProfile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
@JoinColumn(name = "mission_profile_id")
private Set<FlightRangeShare> flightRangeShares;
@JsonIgnore
@NotNull
@ManyToOne(optional = false)
private AircraftSeries aircraftSeries;
}
@Entity
@Table(name = "aircraft_series")
@Getter
@Setter
@NoArgsConstructor
@ToString
public class AircraftSeries {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@Column(unique = true)
@Enumerated(EnumType.STRING)
private AircraftSeriesEnum aircraftSeriesEnum;
}
我正在尝试访问cabinLayout
实体的missionProfile
属性的aircraftSeries
子属性:
public int calcProportionalFuelConsumptionInKg(Long cabinLayoutId, FlightRangeEnum flightRangeEnum) {
return cabinLayoutRepository.findById(cabinLayoutId).map(cabinLayout -> {
missionProfileRepository.findById(cabinLayout.getMissionProfile().getId()).map(missionProfile -> {
int aircraftWeightInKg = missionProfile.getAircraftSeries().getAircraftSeriesEnum()
.getZeroFuelWeightInKg(); // The aircraftWeightInKg is set all right
aircraftWeightInKg = cabinLayout.getMissionProfile().getAircraftSeries().getAircraftSeriesEnum()
.getZeroFuelWeightInKg(); // The getAircraftSeries() returns null
return aircraftWeightInKg;
})
但是,当对cabinLayout
实体执行findById时,它只找到其missionProfile
属性,并将aircraftSeries
子属性留空。
只有在对missionProfile
执行额外的伪冗余findById时,才会获取aircraftSeries
属性。
提取仅在一个层次上完成,提取实体属性,但不提取实体属性子属性。
cabinLayout
变量上的调试器显示:
id: Long@219
missionProfile: MissionProfile@220 "MissionProfile(id=1, aircraftSeries=null)"
aircraftSeries: null
我知道调试器正在代理,并且不会在其视图中触发获取,所以我让它运行测试直到失败。我还可以看到在终端中运行测试时的错误:
2022-10-07 16:30:17,894 ERROR [main] com.exception.ControllerExceptionHandler: Uncatched exception
java.lang.NullPointerException: null
at com.service.EquipmentCalculatorService.calcEquipmentProportionalFuelConsumptionInKg(EquipmentCalculatorService.java:143)
我还尝试了一些明确的加入和渴望的获取,但这丝毫没有改变。
@JsonIgnore
@NotNull
@Fetch(FetchMode.JOIN)
@ManyToOne(optional = false, fetch = FetchType.EAGER)
private MissionProfile missionProfile;
@JsonIgnore
@NotNull
@Fetch(FetchMode.JOIN)
@ManyToOne(optional = false, fetch = FetchType.EAGER)
private AircraftSeries aircraftSeries;
奇怪的是,missionProfile
实体实际上有另一个集合属性,可以正常提取:
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
@JoinColumn(name = "mission_profile_id")
private Set<FlightRangeShare> flightRangeShares;
我使用的是Java 11 中的spring-boot starter parent 2.7.0
更新:根据grigouille的回复,我创建了存储库:
@Query("SELECT cl FROM CabinLayout cl LEFT JOIN FETCH cl.missionProfile mp LEFT JOIN FETCH mp.aircraftSeries WHERE cl.id = :id")
Optional<CabinLayout> findByIdFetching(@Param("id") Long id);
我称之为:
return cabinLayoutRepository.findByIdFetching(cabinLayoutId).map(cabinLayout -> {
但它仍然给出了完全相同的错误。
日志显示实际的SQL语句:
select cabinlayou0_.id as id1_5_0_, missionpro1_.id as id1_18_1_, aircraftse2_.id as id1_1_2_, cabinlayou0_.description as descript2_5_0_, cabinlayou0_.mission_profile_id as mission_7_5_0_, cabinlayou0_.name as name3_5_0_, cabinlayou0_.tags as tags4_5_0_, cabinlayou0_.user_id as user_id5_5_0_, cabinlayou0_.workflow_status_enum as workflow6_5_0_, missionpro1_.aircraft_series_id as aircraft5_18_1_, missionpro1_.flight_cycles as flight_c2_18_1_, missionpro1_.lifetime as lifetime3_18_1_, missionpro1_.name as name4_18_1_, aircraftse2_.aircraft_series_enum as aircraft2_1_2_ from cabin_layout cabinlayou0_ left outer join mission_profile missionpro1_ on cabinlayou0_.mission_profile_id=missionpro1_.id left outer join aircraft_series aircraftse2_ on missionpro1_.aircraft_series_id=aircraftse2_.id where cabinlayou0_.id=?
更新:在评论之后,我还尝试添加属性:
spring:
jpa:
hibernate:
max_fetch_depth: 3
hibernate:
max_fetch_depth: 3
但它什么也没改变。
更新:我尝试了一个投影的变通方法。
@Query("SELECT new com.domain.projection.CabinLayoutView(cl, mp, ars, ce, e) FROM CabinEquipment ce JOIN ce.equipment e JOIN ce.cabinLayout cl JOIN cl.missionProfile mp JOIN mp.aircraftSeries ars WHERE cl.id = :id")
List<CabinLayoutView> findByIdFetching(@Param("id") Long id);
然后我可以重建我的实体:
public CabinLayout findByIdFetching(Long cabinLayoutId) {
return toCabinLayout(cabinLayoutRepository.findByIdFetching(cabinLayoutId));
}
private CabinLayout toCabinLayout(List<CabinLayoutView> cabinLayoutViews) {
if (!cabinLayoutViews.isEmpty()) {
CabinLayout cabinLayout = null;
Set<CabinEquipment> cabinEquipments = new HashSet<>();
for (CabinLayoutView cabinLayoutView : cabinLayoutViews) {
cabinLayout = cabinLayoutView.getCabinLayout();
cabinLayout.setMissionProfile(cabinLayoutView.getMissionProfile());
cabinLayout.getMissionProfile().setAircraftSeries(cabinLayoutView.getAircraftSeries());
CabinEquipment cabinEquipment = cabinLayoutView.getCabinEquipment();
cabinEquipment.setEquipment(cabinLayoutView.getEquipment());
cabinEquipments.add(cabinEquipment);
}
if (Objects.nonNull(cabinLayout)) {
cabinLayout.getCabinEquipments().clear();
cabinLayout.getCabinEquipments().addAll(cabinEquipments);
}
return cabinLayout;
} else {
throw new NotFoundException("The cabin layout projection was empty");
}
}
尝试在一个查询中获取所有属性:
EntityManager em;
Long id;
CabinLayout cabinLayout = em.createQuery("select c from CabinLayout c left join fetch c.missionProfile m left join fetch m.aircraftSeries where c.id = :id", CabinLayout.class)
.setParameter("id", id)
.getSingleResult();
//load flightRangeShares if you want
cabinLayout.getMissionProfile().getFlightRangeShares().size();
return cabinLayout